Repository: graphql-rust/juniper (language: rust)

Issue:
Re-enable interface tests in the book
These were disabled in https://github.com/graphql-rust/juniper/pull/543. Once `juniper::graphql_interface` is implemented, re-enable.


Discussion / hints:

Fix patch:
diff --git a/docs/book/content/advanced/introspection.md b/docs/book/content/advanced/introspection.md
index 279b96b14..9bf7e8956 100644
--- a/docs/book/content/advanced/introspection.md
+++ b/docs/book/content/advanced/introspection.md
@@ -30,6 +30,7 @@ result can then be converted to JSON for use with tools and libraries such as
 [graphql-client](https://github.com/graphql-rust/graphql-client):
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 # extern crate serde_json;
 use juniper::{EmptyMutation, EmptySubscription, FieldResult, IntrospectionFormat};
diff --git a/docs/book/content/advanced/subscriptions.md b/docs/book/content/advanced/subscriptions.md
index 162d57fa8..59f145161 100644
--- a/docs/book/content/advanced/subscriptions.md
+++ b/docs/book/content/advanced/subscriptions.md
@@ -78,6 +78,7 @@ operation returns a [`Future`][Future] with an `Item` value of a `Result<Connect
 where [`Connection`][Connection] is a `Stream` of values returned by the operation and [`GraphQLError`][GraphQLError] is the error when the subscription fails.
 
 ```rust
+# #![allow(dead_code)]
 # extern crate futures;
 # extern crate juniper;
 # extern crate juniper_subscriptions;
@@ -88,8 +89,6 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
 # use juniper_subscriptions::Coordinator;
 # use futures::{Stream, StreamExt};
 # use std::pin::Pin;
-# use tokio::runtime::Runtime;
-# use tokio::task;
 # 
 # #[derive(Clone)]
 # pub struct Database;
diff --git a/docs/book/content/quickstart.md b/docs/book/content/quickstart.md
index 2b7c57279..b3b580152 100644
--- a/docs/book/content/quickstart.md
+++ b/docs/book/content/quickstart.md
@@ -26,6 +26,7 @@ types to a GraphQL schema. The most important one is the
 resolvers, which you will use for the `Query` and `Mutation` roots.
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 use juniper::{FieldResult, EmptySubscription};
 
diff --git a/docs/book/content/schema/schemas_and_mutations.md b/docs/book/content/schema/schemas_and_mutations.md
index c008dcb4b..77cb45571 100644
--- a/docs/book/content/schema/schemas_and_mutations.md
+++ b/docs/book/content/schema/schemas_and_mutations.md
@@ -22,6 +22,7 @@ The query root is just a GraphQL object. You define it like any other GraphQL
 object in Juniper, most commonly using the `graphql_object` proc macro:
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 # use juniper::FieldResult;
 # #[derive(juniper::GraphQLObject)] struct User { name: String }
@@ -44,6 +45,7 @@ Mutations are _also_ just GraphQL objects. Each mutation is a single field
 that performs some mutating side-effect such as updating a database.
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 # use juniper::FieldResult;
 # #[derive(juniper::GraphQLObject)] struct User { name: String }
diff --git a/docs/book/content/types/input_objects.md b/docs/book/content/types/input_objects.md
index 00b478c0e..ef103aacf 100644
--- a/docs/book/content/types/input_objects.md
+++ b/docs/book/content/types/input_objects.md
@@ -5,6 +5,7 @@ GraphQL fields. In Juniper, you can define input objects using a custom derive
 attribute, similar to simple objects and enums:
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 #[derive(juniper::GraphQLInputObject)]
 struct Coordinate {
@@ -33,6 +34,7 @@ Just like the [other](objects/defining_objects.md) [derives](enums.md), you can
 and add documentation to both the type and the fields:
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 #[derive(juniper::GraphQLInputObject)]
 #[graphql(name="Coordinate", description="A position on the globe")]
diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md
index 0cc79a890..921cdb9f5 100644
--- a/docs/book/content/types/interfaces.md
+++ b/docs/book/content/types/interfaces.md
@@ -1,16 +1,280 @@
-# Interfaces
+Interfaces
+==========
+
+[GraphQL interfaces][1] map well to interfaces known from common object-oriented languages such as Java or C#, but Rust, unfortunately, has no concept that maps perfectly to them. The nearest analogue of [GraphQL interfaces][1] are Rust traits, and the main difference is that in GraphQL [interface type][1] serves both as an _abstraction_ and a _boxed value (downcastable to concrete implementers)_, while in Rust, a trait is an _abstraction only_ and _to represent such a boxed value a separate type is required_, like enum or trait object, because Rust trait does not represent a type itself, and so can have no values. This difference imposes some unintuitive and non-obvious corner cases when we try to express [GraphQL interfaces][1] in Rust, but on the other hand gives you full control over which type is backing your interface, and how it's resolved.
+
+For implementing [GraphQL interfaces][1] Juniper provides `#[graphql_interface]` macro.
+
 
-GraphQL interfaces map well to interfaces known from common object-oriented
-languages such as Java or C#, but Rust has unfortunately not a concept that maps
-perfectly to them. Because of this, defining interfaces in Juniper can require a
-little bit of boilerplate code, but on the other hand gives you full control
-over which type is backing your interface.
 
-To highlight a couple of different ways you can implement interfaces in Rust,
-let's have a look at the same end-result from a few different implementations:
 
 ## Traits
 
+Defining a trait is mandatory for defining a [GraphQL interface][1], because this is the _obvious_ way we describe an _abstraction_ in Rust. All [interface][1] fields are defined as computed ones via trait methods.
+
+```rust
+# extern crate juniper;
+use juniper::graphql_interface;
+
+#[graphql_interface]
+trait Character {
+    fn id(&self) -> &str;
+}
+#
+# fn main() {}
+```
+
+However, to return values of such [interface][1], we should provide its implementers and the Rust type representing a _boxed value of this trait_. The last one can be represented in two flavors: enum and [trait object][2].
+
+
+### Enum values (default)
+
+By default, Juniper generates an enum representing the values of the defined [GraphQL interface][1], and names it straightforwardly, `{Interface}Value`.
+
+```rust
+# extern crate juniper;
+use juniper::{graphql_interface, GraphQLObject};
+
+#[graphql_interface(for = Human)] // enumerating all implementers is mandatory 
+trait Character {
+    fn id(&self) -> &str;
+}
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharacterValue)] // notice enum name, NOT trait name
+struct Human {
+    id: String,
+}
+#[graphql_interface] // implementing requires macro attribute too, (°ｏ°)!
+impl Character for Human {
+    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");
+# }
+```
+
+Also, enum name can be specified explicitly, if desired.
+
+```rust
+# extern crate juniper;
+use juniper::{graphql_interface, GraphQLObject};
+
+#[graphql_interface(enum = CharaterInterface, for = Human)] 
+trait Character {
+    fn id(&self) -> &str;
+}
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharaterInterface)]
+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, that 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
+    }
+}
+#
+# #[tokio::main]
+# async fn main() {
+let human = Human { id: "human-32".to_owned() };
+let character: Box<DynCharacter> = 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.
+
+```rust
+# extern crate juniper;
+use juniper::{graphql_interface, GraphQLObject};
+
+#[graphql_interface(for = Human)]  
+trait Character {
+    fn id(&self) -> &str;
+
+    #[graphql_interface(ignore)] // or `#[graphql_interface(skip)]`, your choice
+    fn ignored(&self) -> u32 { 0 }
+}
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharacterValue)]
+struct Human {
+    id: String,
+}
+#[graphql_interface] // implementing requires macro attribute too, (°ｏ°)!
+impl Character for Human {
+    fn id(&self) -> &str {
+        &self.id
+    }
+}
+#
+# fn main() {}
+```
+
+
+### Custom context
+
+If a context is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
+
+```rust
+# extern crate juniper;
+# use std::collections::HashMap;
+use juniper::{graphql_interface, GraphQLObject};
+
+struct Database {
+    humans: HashMap<String, Human>,
+}
+impl juniper::Context for Database {}
+
+#[graphql_interface(for = Human)] // look, ma, context type is inferred! ＼(^o^)／
+trait Character {                 // while still can be specified via `Context = ...` attribute argument
+    // If a field argument is named `context` or `ctx`, it's automatically assumed
+    // as a context argument.
+    fn id(&self, context: &Database) -> Option<&str>;
+
+    // Otherwise, you may mark it explicitly as a context argument.
+    fn name(&self, #[graphql_interface(context)] db: &Database) -> Option<&str>;
+}
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharacterValue, Context = Database)]
+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() {}
+```
+
+
+### Using executor and explicit generic scalar
+
+If an executor is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
+
+This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`][4] does so. 
+
+```rust
+# extern crate juniper;
+use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
+
+#[graphql_interface(for = Human, Scalar = S)] // notice specifying scalar as existing type parameter
+trait Character<S: ScalarValue> {             
+    // 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 ¯\_(ツ)_/¯
+    
+
+    // Otherwise, you may mark it explicitly as an executor argument.
+    async fn name<'b>(
+        &'b self,
+        #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>,
+    ) -> &'b str
+    where
+        S: Send + Sync;
+}
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharacterValue<__S>)]
+struct Human {
+    id: String,
+    name: String,
+}
+#[graphql_interface(Scalar = S)]
+impl<S: ScalarValue> Character<S> for Human {
+    async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
+    where
+        S: Send + Sync,
+    {
+        executor.look_ahead().field_name()
+    }
+
+    async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+    where
+        S: Send + Sync,
+    {
+        &self.name
+    }
+}
+#
+# fn main() {}
+```
+
+
+
+
+
+
 Traits are maybe the most obvious concept you want to use when building
 interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
 have to manually specify how to convert a trait into a concrete type. This can
@@ -63,7 +327,7 @@ juniper::graphql_interface!(<'a> &'a dyn Character: () as "Character" where Scal
 # fn main() {}
 ```
 
-The `instance_resolvers` declaration lists all the implementors of the given
+The `instance_resolvers` declaration lists all the implementers of the given
 interface and how to resolve them.
 
 As you can see, you lose a bit of the point with using traits: you need to list
@@ -213,3 +477,12 @@ juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
 
 # fn main() {}
 ```
+
+
+
+
+
+[1]: https://spec.graphql.org/June2018/#sec-Interfaces
+[2]: https://doc.rust-lang.org/reference/types/trait-object.html
+[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
+[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html
\ No newline at end of file
diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md
index a33ba0479..6abc6a047 100644
--- a/docs/book/content/types/objects/complex_fields.md
+++ b/docs/book/content/types/objects/complex_fields.md
@@ -10,8 +10,9 @@ example from the last chapter, this is how you would define `Person` using the
 macro:
 
 ```rust
+# #![allow(dead_code)]
 # extern crate juniper;
-
+#
 struct Person {
     name: String,
     age: i32,
diff --git a/docs/book/content/types/unions.md b/docs/book/content/types/unions.md
index ab7ef2f3b..e8209dabd 100644
--- a/docs/book/content/types/unions.md
+++ b/docs/book/content/types/unions.md
@@ -16,7 +16,7 @@ Most of the time, we just need a trivial and straightforward Rust enum to repres
 
 ```rust
 # extern crate juniper;
-# #[macro_use] extern crate derive_more;
+# extern crate derive_more;
 use derive_more::From;
 use juniper::{GraphQLObject, GraphQLUnion};
 
@@ -53,7 +53,7 @@ As an example, let's consider the situation where we need to bind some type para
 
 ```rust
 # extern crate juniper;
-# #[macro_use] extern crate derive_more;
+# extern crate derive_more;
 # use std::marker::PhantomData;
 use derive_more::From;
 use juniper::{GraphQLObject, GraphQLUnion};
@@ -88,6 +88,7 @@ enum Character<S> {
 If some custom logic is needed to resolve a [GraphQL union][1] variant, you may specify an external function to do so:
 
 ```rust
+# #![allow(dead_code)]
 # extern crate juniper;
 use juniper::{GraphQLObject, GraphQLUnion};
 
@@ -132,6 +133,7 @@ impl Character {
 With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/June2018/#example-f8163).
 
 ```rust
+# #![allow(dead_code)]
 # extern crate juniper;
 use juniper::{GraphQLObject, GraphQLUnion};
 
@@ -289,6 +291,7 @@ impl Character for Droid {
 If a context is required in a trait method to resolve a [GraphQL union][1] variant, specify it as an argument.
 
 ```rust
+# #![allow(unused_variables)]
 # extern crate juniper;
 # use std::collections::HashMap;
 use juniper::{graphql_union, GraphQLObject};
@@ -451,6 +454,7 @@ fn get_droid<'db>(ch: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droi
 By default, `#[derive(GraphQLUnion)]` and `#[graphql_union]` macros generate code, which is generic over a [`ScalarValue`][2] type. This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`][2] type in its implementation. To resolve such problem, a concrete [`ScalarValue`][2] type should be specified:
 
 ```rust
+# #![allow(dead_code)]
 # extern crate juniper;
 use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
 
diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs
index 152cf22c6..2c2c0d7f1 100644
--- a/examples/actix_subscriptions/src/main.rs
+++ b/examples/actix_subscriptions/src/main.rs
@@ -4,7 +4,7 @@ use actix_cors::Cors;
 use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
 
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Character as _, Database, Query},
     DefaultScalarValue, EmptyMutation, FieldError, RootNode,
 };
 use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
@@ -76,11 +76,11 @@ impl Subscription {
                 ))
             } else {
                 let random_id = rng.gen_range(1000, 1005).to_string();
-                let human = context.get_human(&random_id).unwrap();
+                let human = context.get_human(&random_id).unwrap().clone();
 
                 Ok(RandomHuman {
                     id: human.id().to_owned(),
-                    name: human.name().to_owned(),
+                    name: human.name().unwrap().to_owned(),
                 })
             }
         });
diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml
index 882abe6eb..332454a61 100644
--- a/juniper/Cargo.toml
+++ b/juniper/Cargo.toml
@@ -18,43 +18,44 @@ edition = "2018"
 [badges]
 travis-ci = { repository = "graphql-rust/juniper" }
 
-[[bench]]
-name = "bench"
-harness = false
-path = "benches/bench.rs"
-
 [features]
-expose-test-schema = ["anyhow", "serde_json"]
-schema-language = ["graphql-parser-integration"]
-graphql-parser-integration = ["graphql-parser"]
 default = [
     "bson",
     "chrono",
+    "schema-language",
     "url",
     "uuid",
-    "schema-language",
 ]
+expose-test-schema = ["anyhow", "serde_json"]
+graphql-parser-integration = ["graphql-parser"]
 scalar-naivetime = []
+schema-language = ["graphql-parser-integration"]
 
 [dependencies]
 juniper_codegen = { version = "0.14.2", path = "../juniper_codegen"  }
 
 anyhow = { default-features = false, version = "1.0.32", optional = true }
+async-trait = "0.1.39"
 bson = { version = "1.0", optional = true }
 chrono = { default-features = false, version = "0.4", optional = true }
 fnv = "1.0.3"
 futures = { default-features = false, features = ["alloc"], version = "0.3.1" }
 futures-enum = "0.1.12"
+graphql-parser = { version = "0.3", optional = true }
 indexmap = { version = "1.0", features = ["serde-1"] }
-serde = { default-features = false, version = "1.0.8", features = ["derive"] }
-serde_json = { default-features = false, version = "1.0", optional = true }
+serde = { version = "1.0.8", features = ["derive"], default-features = false }
+serde_json = { version = "1.0.2", default-features = false, optional = true }
 static_assertions = "1.1"
 url = { version = "2.0", optional = true }
-uuid = { default-features = false, version = "0.8", optional = true }
-graphql-parser = { version = "0.3", optional = true }
+uuid = { version = "0.8", default-features = false, optional = true }
 
 [dev-dependencies]
 bencher = "0.1.2"
+pretty_assertions = "0.6.1"
 serde_json = { version = "1.0.2" }
 tokio = { version = "0.2", features = ["macros", "rt-core", "time"] }
-pretty_assertions = "0.6.1"
+
+[[bench]]
+name = "bench"
+harness = false
+path = "benches/bench.rs"
diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs
index 07be7ac9e..92a9648fa 100644
--- a/juniper/src/executor/mod.rs
+++ b/juniper/src/executor/mod.rs
@@ -1025,10 +1025,9 @@ where
 
     let mut fragments = vec![];
     for def in document.iter() {
-        match def {
-            Definition::Fragment(f) => fragments.push(f),
-            _ => (),
-        };
+        if let Definition::Fragment(f) = def {
+            fragments.push(f)
+        }
     }
 
     let default_variable_values = operation.item.variable_definitions.as_ref().map(|defs| {
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index 154ab67c7..5ac999b7b 100644
--- a/juniper/src/lib.rs
+++ b/juniper/src/lib.rs
@@ -119,7 +119,7 @@ extern crate bson;
 
 // These are required by the code generated via the `juniper_codegen` macros.
 #[doc(hidden)]
-pub use {futures, static_assertions as sa};
+pub use {async_trait::async_trait, futures, static_assertions as sa};
 
 #[doc(inline)]
 pub use futures::future::{BoxFuture, LocalBoxFuture};
@@ -128,8 +128,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_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,
 };
 
 #[macro_use]
@@ -175,15 +175,18 @@ pub use crate::{
         LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables,
     },
     introspection::IntrospectionFormat,
-    macros::subscription_helpers::{ExtractTypeFromStream, IntoFieldResult},
+    macros::helper::{
+        subscription::{ExtractTypeFromStream, IntoFieldResult},
+        AsDynGraphQLValue,
+    },
     schema::{
         meta,
         model::{RootNode, SchemaType},
     },
     types::{
-        async_await::{GraphQLTypeAsync, GraphQLValueAsync},
-        base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
-        marker::{self, GraphQLUnion, IsOutputType},
+        async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync},
+        base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind},
+        marker::{self, GraphQLInterface, GraphQLUnion},
         scalars::{EmptyMutation, EmptySubscription, ID},
         subscriptions::{
             ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
diff --git a/juniper/src/macros/common.rs b/juniper/src/macros/common.rs
deleted file mode 100644
index 9178f5a78..000000000
--- a/juniper/src/macros/common.rs
+++ /dev/null
@@ -1,741 +0,0 @@
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __juniper_impl_trait {
-    (
-        impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty {
-            $($body:tt)+
-        }
-    ) => {
-        impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name {
-            $($body)*
-        }
-    };
-    (
-        impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty
-            where ( $($where:tt)* )
-        {
-            $($body:tt)+
-        }
-    ) => {
-        impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name
-            where $($where)*
-        {
-            $($body)*
-        }
-    };
-
-    (
-        impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty {
-            $($body:tt)*
-        }
-    ) => {
-       impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name
-        where
-            $generic: $crate::ScalarValue,
-       {
-           $($body)*
-       }
-    };
-    (
-        impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty
-            where ( $($where:tt)* )
-        {
-            $($body:tt)*
-        }
-    ) => {
-       impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name
-        where
-            $($where)*
-            $generic: $crate::ScalarValue,
-       {
-           $($body)*
-       }
-    };
-
-    (
-        impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty {
-            $($body:tt)*
-        }
-    ) => {
-        impl<$($other, )*> $crate::$impl_trait<$scalar> for $name {
-            $($body)*
-        }
-    };
-    (
-        impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty
-            where ( $($where:tt)* )
-        {
-            $($body:tt)*
-        }
-    ) => {
-        impl<$($other, )*> $crate::$impl_trait<$scalar> for $name
-            where $($where)*
-        {
-            $($body)*
-        }
-    };
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __juniper_insert_generic {
-    (<DefaultScalarValue>) => {
-        $crate::DefaultScalarValue
-    };
-    (
-        <$generic:tt $(: $bound: tt)*>
-    ) => {
-        $generic
-    };
-    (
-        $scalar: ty
-    ) => {
-        $scalar
-    };
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __juniper_parse_object_header {
-    (
-        callback = $callback:ident,
-        rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt
-        where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {$outname},
-                scalar = {<$generic $(: $bound)*>},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback:ident,
-        rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt
-        where Scalar = $scalar: ty $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {$outname},
-                scalar = {$scalar},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {$outname},
-                scalar = {<DefaultScalarValue>},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = $name: ty $(: $ctxt: ty)* as $outname: tt
-        where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {$outname},
-                scalar = {<$generic $(:$bound)*>},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = $name: ty $(: $ctxt: ty)* as $outname: tt
-        where Scalar = $scalar: ty $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {$outname},
-                scalar = {$scalar},
-            },
-            rest = $($items)*
-        );
-    };
-
-
-    (
-        callback = $callback: ident,
-        rest = $name: ty $(: $ctxt: ty)* as $outname: tt $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {$outname},
-                scalar = {<DefaultScalarValue>},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)*
-        where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {stringify!($name)},
-                scalar = {<$generic $(:$bounds)*>},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)*
-        where Scalar = $scalar: ty $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {stringify!($name)},
-                scalar = {$scalar},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {stringify!($name)},
-                scalar = {<DefaultScalarValue>},
-            },
-            rest = $($items)*
-        );
-    };
-
-
-    (
-        callback = $callback: ident,
-        rest = $name: ty $(: $ctxt: ty)*
-        where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)*
-        {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {stringify!($name)},
-                scalar = {<$generic $(: $bound)*>},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = $name: ty $(: $ctxt: ty)* where Scalar = $scalar: ty $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {stringify!($name)},
-                scalar = {$scalar},
-            },
-            rest = $($items)*
-        );
-    };
-
-    (
-        callback = $callback: ident,
-        rest = $name: ty $(: $ctxt: ty)* $(| &$mainself:ident |)* {
-            $($items: tt)*
-        }
-    ) => {
-        $crate::$callback!(
-            @parse,
-            meta = {
-                lifetimes = [],
-                name = $name,
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                outname = {stringify!($name)},
-                scalar = {<DefaultScalarValue>},
-            },
-            rest = $($items)*
-        );
-    };
-    (
-        callback = $callback: ident,
-        rest = $($rest:tt)*
-    ) => {
-        compile_error!("Invalid syntax");
-    };
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __juniper_parse_field_list {
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest =
-    ) => {
-        $crate::$success_callback!(
-            @generate,
-            meta = {$($meta)*},
-            items = [$({$($items)*},)*],
-        );
-    };
-
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = , $($rest: tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {$($meta)*},
-            items = [$({$($items)*},)*],
-            rest = $($rest)*
-        );
-    };
-
-
-    (
-        @parse_description,
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {
-            $(lifetimes = [$($lifetime:tt,)*],)*
-            $(name = $name:ty,)*
-            $(ctx = $ctxt: ty,)*
-            $(main_self = $mainself: ident,)*
-            $(outname = {$($outname:tt)*},)*
-            $(scalar = {$($scalar:tt)*},)*
-            $(description = $_desciption: tt,)*
-            $(additional = {$($other: tt)*},)*
-        },
-        items = [$({$($items: tt)*},)*],
-        rest = $desc: tt  $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {
-                $(lifetimes = [$($lifetime,)*],)*
-                $(name = $name,)*
-                $(ctx = $ctxt,)*
-                $(main_self = $mainself,)*
-                $(outname = {$($outname)*},)*
-                $(scalar = {$($scalar)*},)*
-                description = $desc,
-                $(additional = {$($other)*},)*
-
-            },
-            items = [$({$($items)*},)*],
-            rest = $($rest)*
-        );
-    };
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = { $($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = description:  $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            @parse_description,
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {$($meta)*},
-            items = [$({$($items)*},)*],
-            rest = $($rest)*
-        );
-    };
-
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = $(#[doc = $desc: tt])*
-        #[deprecated $(( $(since = $since: tt,)* note = $reason: tt ))* ]
-        field $name: ident (
-            $(&$executor: tt)* $(,)*
-            $($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)*
-        ) -> $return_ty: ty $body: block
-            $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {$($meta)*},
-            items = [$({$($items)*},)* {
-                name = $name,
-                body = $body,
-                return_ty = $return_ty,
-                args = [
-                    $({
-                        arg_name = $arg_name,
-                        arg_ty = $arg_ty,
-                        $(arg_default = $arg_default,)*
-                        $(arg_docstring = $arg_desc,)*
-                    },)*
-                ],
-                $(docstring = $desc,)*
-                deprecated = None$(.unwrap_or_else(|| Some($reason)))*,
-                $(executor_var = $executor,)*
-            },],
-            rest = $($rest)*
-        );
-    };
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = $(#[doc = $desc: tt])*
-        field $name: ident (
-            $(&$executor: ident)* $(,)*
-            $($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)*
-        ) -> $return_ty: ty $body: block
-            $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {$($meta)*},
-            items = [$({$($items)*},)* {
-                name = $name,
-                body = $body,
-                return_ty = $return_ty,
-                args = [
-                    $({
-                        arg_name = $arg_name,
-                        arg_ty = $arg_ty,
-                        $(arg_default = $arg_default,)*
-                        $(arg_docstring = $arg_desc,)*
-                    },)*
-                ],
-                $(docstring = $desc,)*
-                $(executor_var = $executor,)*
-            },],
-            rest = $($rest)*
-        );
-    };
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = field deprecated $reason:tt $name: ident (
-            $(&$executor: tt)* $(,)*
-            $($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
-        ) -> $return_ty: ty $(as $desc: tt)* $body: block
-            $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {$($meta)*},
-            items = [$({$($items)*},)* {
-                name = $name,
-                body = $body,
-                return_ty = $return_ty,
-                args = [
-                    $({
-                        arg_name = $arg_name,
-                        arg_ty = $arg_ty,
-                        $(arg_default = $arg_default,)*
-                        $(arg_description = $arg_desc,)*
-                    },)*
-                ],
-                $(decs = $desc,)*
-                deprecated = Some($reason),
-                $(executor_var = $executor,)*
-            },],
-            rest = $($rest)*
-        );
-    };
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = field $name: ident (
-            $(&$executor: ident)* $(,)*
-            $($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
-        ) -> $return_ty: ty $(as $desc: tt)* $body: block
-            $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {$($meta)*},
-            items = [$({$($items)*},)* {
-                name = $name,
-                body = $body,
-                return_ty = $return_ty,
-                args = [
-                    $({
-                        arg_name = $arg_name,
-                        arg_ty = $arg_ty,
-                        $(arg_default = $arg_default,)*
-                        $(arg_description = $arg_desc,)*
-                    },)*
-                ],
-                $(decs = $desc,)*
-                $(executor_var = $executor,)*
-            },],
-            rest = $($rest)*
-        );
-    };
-
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {
-            callback = $callback: ident,
-            header = {$($header:tt)*},
-        },
-        meta = {$($meta:tt)*},
-        items = [$({$($items: tt)*},)*],
-        rest = $($rest:tt)*
-    ) => {
-        $crate::$callback!(
-            $($header)*
-            success_callback = $success_callback,
-            additional_parser = {
-                callback = $callback,
-                header = {$($header)*},
-            },
-            meta = {$($meta)*},
-            items = [$({$($items)*},)*],
-            rest = $($rest)*
-        );
-    }
-
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __juniper_parse_instance_resolver {
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {
-            lifetimes = [$($lifetime:tt,)*],
-            name = $name:ty,
-            ctx = $ctxt:ty,
-            main_self = $mainself:ident,
-            outname = {$($outname:tt)*},
-            scalar = {$($scalar:tt)*},
-            $(description = $desciption:tt,)*
-                $(additional = {
-                    $(resolver = {$($ignored_resolver:tt)*},)*
-                },)*
-
-        },
-        items = [$({$($items: tt)*},)*],
-        rest = instance_resolvers: |&$context: ident| {
-            $( $srctype:ty => $resolver:expr ),* $(,)*
-        } $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                ctx = $ctxt,
-                main_self = $mainself,
-                outname = {$($outname)*},
-                scalar = {$($scalar)*},
-                $(description = $desciption,)*
-                additional = {
-                    resolver = {
-                        context = $context,
-                        items = [
-                            $({
-                                src = $srctype,
-                                resolver = $resolver,
-                            },)*
-                        ],
-                    },
-                },
-            },
-            items = [$({$($items)*},)*],
-            rest = $($rest)*
-        );
-    };
-
-    (
-        success_callback = $success_callback: ident,
-        additional_parser = {$($additional:tt)*},
-        meta = {
-            lifetimes = [$($lifetime:tt,)*],
-            name = $name:ty,
-            ctx = $ctxt:ty,
-            main_self = $mainself:ident,
-            outname = {$($outname:tt)*},
-            scalar = {$($scalar:tt)*},
-            $(description = $desciption:tt,)*
-            $(additional = {
-                $(resolver = {$($ignored_resolver:tt)*},)*
-            },)*
-
-        },
-        items = [$({$($items: tt)*},)*],
-        rest = instance_resolvers: |$(&)* _| {$( $srctype:ty => $resolver:expr ),* $(,)*} $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = $success_callback,
-            additional_parser = {$($additional)*},
-            meta = {
-                lifetimes = [$($lifetime,)*],
-                name = $name,
-                ctx = $ctxt,
-                main_self = $mainself,
-                outname = {$($outname)*},
-                scalar = {$($scalar)*},
-                $(description = $desciption,)*
-                additional = {
-                    resolver = {
-                        items = [
-                            $({
-                                src = $srctype,
-                                resolver = $resolver,
-                            },)*
-                        ],
-                    },
-                },
-            },
-            items = [$({$($items)*},)*],
-            rest = $($rest)*
-        );
-    };
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __juniper_create_arg {
-    (
-        registry = $reg: ident,
-        info = $info: ident,
-        arg_ty = $arg_ty: ty,
-        arg_name = $arg_name: ident,
-        $(description = $arg_description: expr,)*
-        $(docstring = $arg_docstring: expr,)*
-    ) => {
-        $reg.arg::<$arg_ty>(
-            &$crate::to_camel_case(stringify!($arg_name)),
-            $info,
-        )
-        $(.description($arg_description))*
-        .push_docstring(&[$($arg_docstring,)*])
-    };
-
-    (
-        registry = $reg: ident,
-        info = $info: ident,
-        arg_ty = $arg_ty: ty,
-        arg_name = $arg_name: ident,
-        default = $arg_default: expr,
-        $(description = $arg_description: expr,)*
-        $(docstring = $arg_docstring: expr,)*
-    ) => {
-        $reg.arg_with_default::<$arg_ty>(
-            &$crate::to_camel_case(stringify!($arg_name)),
-            &($arg_default),
-            $info,
-        )
-        $(.description($arg_description))*
-        .push_docstring(&[$($arg_docstring,)*])
-    };
-}
diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs
new file mode 100644
index 000000000..8162b7a9c
--- /dev/null
+++ b/juniper/src/macros/helper/mod.rs
@@ -0,0 +1,34 @@
+//! Helper traits and definitions for macros.
+
+pub mod subscription;
+
+use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ScalarValue};
+
+/// Conversion of a [`GraphQLValue`] to its [trait object][1].
+///
+/// [`GraphQLValue`]: crate::GraphQLValue
+/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
+pub trait AsDynGraphQLValue<S: ScalarValue = DefaultScalarValue> {
+    /// Context type of this [`GraphQLValue`].
+    ///
+    /// [`GraphQLValue`]: crate::GraphQLValue
+    type Context;
+
+    /// Schema information type of this [`GraphQLValue`].
+    ///
+    /// [`GraphQLValue`]: crate::GraphQLValue
+    type TypeInfo;
+
+    /// Converts this value to a [`DynGraphQLValue`] [trait object][1].
+    ///
+    /// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
+    fn as_dyn_graphql_value(&self) -> &DynGraphQLValue<S, Self::Context, Self::TypeInfo>;
+
+    /// Converts this value to a [`DynGraphQLValueAsync`] [trait object][1].
+    ///
+    /// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
+    fn as_dyn_graphql_value_async(&self)
+        -> &DynGraphQLValueAsync<S, Self::Context, Self::TypeInfo>;
+}
+
+crate::sa::assert_obj_safe!(AsDynGraphQLValue<Context = (), TypeInfo = ()>);
diff --git a/juniper/src/macros/subscription_helpers.rs b/juniper/src/macros/helper/subscription.rs
similarity index 100%
rename from juniper/src/macros/subscription_helpers.rs
rename to juniper/src/macros/helper/subscription.rs
diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs
deleted file mode 100644
index bca44882b..000000000
--- a/juniper/src/macros/interface.rs
+++ /dev/null
@@ -1,302 +0,0 @@
-/**
-Expose GraphQL interfaces
-
-Mapping interfaces to GraphQL can be tricky: there is no direct counterpart to
-GraphQL interfaces in Rust, and downcasting is not possible in the general case.
-Many other GraphQL implementations in other languages use instance checks and
-either dynamic typing or forced downcasts to support these features.
-
-A GraphQL interface defines fields that the implementing types also need to
-implement. A GraphQL interface also needs to be able to determine the concrete
-type name as well as downcast the general type to the actual concrete type.
-
-## Syntax
-
-See the documentation for [`graphql_object!`][1] on the general item and type
-syntax. `graphql_interface!` requires an additional `instance_resolvers` item,
-and does _not_ support the `interfaces` item.
-
-`instance_resolvers` is a match like structure used to resolve the concrete
-instance type of the interface. It starts with a context argument and continues
-with a number of match arms; on the left side is the indicated type, and on the
-right an expression that resolve into `Option<T>` of the type indicated:
-
-```rust,ignore
-instance_resolvers: |&context| {
-    &Human => context.get_human(self.id()), // returns Option<&Human>
-    &Droid => context.get_droid(self.id()), // returns Option<&Droid>
-},
-```
-
-This is used for both the `__typename` field and when resolving a specialized
-fragment, e.g. `...on Human`. For `__typename`, the resolvers will be executed
-in order - the first one returning `Some` will be the determined type name. When
-resolving fragment type conditions, only the corresponding match arm will be
-executed.
-
-## Example
-
-A simplified extract from the StarWars schema example shows how to use the
-shared context to implement downcasts.
-
-```rust
-# extern crate juniper;
-# use std::collections::HashMap;
-struct Human { id: String }
-struct Droid { id: String }
-struct Database {
-    humans: HashMap<String, Human>,
-    droids: HashMap<String, Droid>,
-}
-
-trait Character {
-    fn id(&self) -> &str;
-}
-
-impl Character for Human {
-    fn id(&self) -> &str { &self.id }
-}
-
-impl Character for Droid {
-    fn id(&self) -> &str { &self.id }
-}
-
-#[juniper::graphql_object(Context = Database)]
-impl Human {
-    fn id(&self) -> &str { &self.id }
-}
-
-#[juniper::graphql_object(
-    name = "Droid",
-    Context = Database,
-)]
-impl Droid {
-    fn id(&self) -> &str { &self.id }
-}
-
-// You can introduce lifetimes or generic parameters by < > before the name.
-juniper::graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| {
-    field id() -> &str { self.id() }
-
-    instance_resolvers: |&context| {
-        &Human => context.humans.get(self.id()),
-        &Droid => context.droids.get(self.id()),
-    }
-});
-
-# fn main() { }
-```
-
-[1]: macro.graphql_object!.html
-
-*/
-#[macro_export]
-macro_rules! graphql_interface {
-
-    (
-        @generate,
-        meta = {
-            lifetimes = [$($lifetimes:tt,)*],
-            name = $name:ty,
-            ctx = $ctx:ty,
-            main_self = $main_self:ident,
-            outname = {$($outname:tt)*},
-            scalar = {$($scalar:tt)*},
-            $(description = $desciption:tt,)*
-            additional = {
-                resolver = {
-                    $(context = $resolver_ctx: ident,)*
-                    items = [
-                        $({
-                            src = $resolver_src: ty,
-                            resolver = $resolver_expr: expr,
-                        },)*
-                    ],
-                 },
-            },
-        },
-        items = [$({
-            name = $fn_name: ident,
-            body = $body: block,
-            return_ty = $return_ty: ty,
-            args = [$({
-                arg_name = $arg_name : ident,
-                arg_ty = $arg_ty: ty,
-                $(arg_default = $arg_default: expr,)*
-                $(arg_description = $arg_description: expr,)*
-                $(arg_docstring = $arg_docstring: expr,)*
-            },)*],
-            $(decs = $fn_description: expr,)*
-            $(docstring = $docstring: expr,)*
-            $(deprecated = $deprecated: expr,)*
-            $(executor_var = $executor: ident,)*
-        },)*],
-    ) => {
-        $crate::__juniper_impl_trait!(
-            impl<$($scalar)* $(, $lifetimes)* > GraphQLType for $name {
-                fn name(_ : &Self::TypeInfo) -> Option<&'static str> {
-                    Some($($outname)*)
-                }
-
-                fn meta<'r>(
-                    info: &Self::TypeInfo,
-                    registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)>
-                ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)>
-                where
-                    $crate::__juniper_insert_generic!($($scalar)+): 'r
-                {
-                    // Ensure all child types are registered
-                    $(
-                        let _ = registry.get_type::<$resolver_src>(info);
-                    )*
-                    let fields = &[$(
-                        registry.field_convert::<$return_ty, _, Self::Context>(
-                            &$crate::to_camel_case(stringify!($fn_name)),
-                            info
-                        )
-                            $(.description($fn_description))*
-                            .push_docstring(&[$($docstring,)*])
-                            $(.deprecated($deprecated))*
-                            $(.argument(
-                                $crate::__juniper_create_arg!(
-                                    registry = registry,
-                                    info = info,
-                                    arg_ty = $arg_ty,
-                                    arg_name = $arg_name,
-                                    $(default = $arg_default,)*
-                                    $(description = $arg_description,)*
-                                    $(docstring = $arg_docstring,)*
-                                )
-                            ))*,
-                    )*];
-                    registry.build_interface_type::<$name>(
-                        info, fields
-                    )
-                        $(.description($desciption))*
-                        .into_meta()
-                }
-            }
-        );
-
-        $crate::__juniper_impl_trait!(
-            impl<$($scalar)* $(, $lifetimes)* > IsOutputType for $name { }
-        );
-
-        $crate::__juniper_impl_trait!(
-            impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name {
-                type Context = $ctx;
-                type TypeInfo = ();
-
-                fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> {
-                    Some($($outname)*)
-                }
-
-                #[allow(unused_variables)]
-                fn resolve_field(
-                    &$main_self,
-                    info: &Self::TypeInfo,
-                    field: &str,
-                    args: &$crate::Arguments<$crate::__juniper_insert_generic!($($scalar)+)>,
-                    executor: &$crate::Executor<Self::Context, $crate::__juniper_insert_generic!($($scalar)+)>
-                ) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)+)> {
-                    $(
-                        if field == &$crate::to_camel_case(stringify!($fn_name)) {
-                            let f = (|| {
-                                $(
-                                    let $arg_name: $arg_ty = args.get(&$crate::to_camel_case(stringify!($arg_name)))
-                                        .expect(concat!(
-                                            "Argument ",
-                                            stringify!($arg_name),
-                                            " missing - validation must have failed"
-                                        ));
-                                )*
-                                $(
-                                    let $executor = &executor;
-                                )*
-                                $body
-                            });
-                            let result: $return_ty = f();
-
-                            return $crate::IntoResolvable::into(result, executor.context())
-                                .and_then(|res| {
-                                    match res {
-                                        Some((ctx, r)) => {
-                                            executor.replaced_context(ctx)
-                                                .resolve_with_ctx(&(), &r)
-                                        }
-                                        None => Ok($crate::Value::null())
-                                    }
-                                });
-                        }
-                    )*
-
-                    panic!("Field {} not found on type {}", field, $($outname)*)
-                }
-
-                #[allow(unused_variables)]
-                fn concrete_type_name(&$main_self, context: &Self::Context, _info: &Self::TypeInfo) -> String {
-                    $(let $resolver_ctx = &context;)*
-
-                    $(
-                        if ($resolver_expr as ::std::option::Option<$resolver_src>).is_some() {
-                            return
-                                <$resolver_src as $crate::GraphQLType<_>>::name(&()).unwrap().to_owned();
-                        }
-                    )*
-
-                    panic!("Concrete type not handled by instance resolvers on {}", $($outname)*);
-                }
-
-                fn resolve_into_type(
-                    &$main_self,
-                    _info: &Self::TypeInfo,
-                    type_name: &str,
-                    _: Option<&[$crate::Selection<$crate::__juniper_insert_generic!($($scalar)*)>]>,
-                    executor: &$crate::Executor<Self::Context, $crate::__juniper_insert_generic!($($scalar)*)>,
-                ) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)*)> {
-                    $(let $resolver_ctx = &executor.context();)*
-
-                    $(
-                        if type_name == (<$resolver_src as $crate::GraphQLType<_>>::name(&())).unwrap() {
-                            return executor.resolve(&(), &$resolver_expr);
-                        }
-                    )*
-
-                     panic!("Concrete type not handled by instance resolvers on {}", $($outname)*);
-                }
-            }
-        );
-    };
-
-    (
-        @parse,
-        meta = {$($meta:tt)*},
-        rest = $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_field_list!(
-            success_callback = graphql_interface,
-            additional_parser = {
-                callback = __juniper_parse_instance_resolver,
-                header = {},
-            },
-            meta = {$($meta)*},
-            items = [],
-            rest = $($rest)*
-        );
-    };
-
-    (@$($stuff:tt)*) => {
-        compile_error!("Invalid syntax for `graphql_interface!`");
-    };
-
-    (
-        $($rest:tt)*
-    ) => {
-        $crate::__juniper_parse_object_header!(
-            callback = graphql_interface,
-            rest = $($rest)*
-        );
-    }
-
-
-}
diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs
index 1d4c57006..b38958dae 100644
--- a/juniper/src/macros/mod.rs
+++ b/juniper/src/macros/mod.rs
@@ -1,12 +1,6 @@
-// Wrapper macros which allows built-in macros to be recognized as "crate-local"
-// and helper traits for #[juniper::graphql_subscription] macro.
+//! Helper definitions for macros.
 
-#[macro_use]
-mod common;
-#[macro_use]
-mod interface;
+pub mod helper;
 
 #[cfg(test)]
 mod tests;
-
-pub mod subscription_helpers;
diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs
index 1f610ed6f..be64e06f8 100644
--- a/juniper/src/schema/meta.rs
+++ b/juniper/src/schema/meta.rs
@@ -639,29 +639,6 @@ impl<'a, S> Field<'a, S> {
         self
     }
 
-    /// Adds a (multi)line doc string to the description of the field.
-    /// Any leading or trailing newlines will be removed.
-    ///
-    /// If the docstring contains newlines, repeated leading tab and space characters
-    /// will be removed from the beginning of each line.
-    ///
-    /// If the description hasn't been set, the description is set to the provided line.
-    /// Otherwise, the doc string is added to the current description after a newline.
-    pub fn push_docstring(mut self, multiline: &[&str]) -> Field<'a, S> {
-        if let Some(docstring) = clean_docstring(multiline) {
-            match &mut self.description {
-                &mut Some(ref mut desc) => {
-                    desc.push('\n');
-                    desc.push_str(&docstring);
-                }
-                desc @ &mut None => {
-                    *desc = Some(docstring);
-                }
-            }
-        }
-        self
-    }
-
     /// Add an argument to the field
     ///
     /// Arguments are unordered and can't contain duplicates by name.
@@ -706,30 +683,9 @@ impl<'a, S> Argument<'a, S> {
         self
     }
 
-    /// Adds a (multi)line doc string to the description of the field.
-    /// Any leading or trailing newlines will be removed.
-    ///
-    /// If the docstring contains newlines, repeated leading tab and space characters
-    /// will be removed from the beginning of each line.
-    ///
-    /// If the description hasn't been set, the description is set to the provided line.
-    /// Otherwise, the doc string is added to the current description after a newline.
-    pub fn push_docstring(mut self, multiline: &[&str]) -> Argument<'a, S> {
-        if let Some(docstring) = clean_docstring(multiline) {
-            match &mut self.description {
-                &mut Some(ref mut desc) => {
-                    desc.push('\n');
-                    desc.push_str(&docstring);
-                }
-                desc @ &mut None => *desc = Some(docstring),
-            }
-        }
-        self
-    }
-
     /// Set the default value of the argument
     ///
-    /// This overwrites the description if any was previously set.
+    /// This overwrites the default value if any was previously set.
     pub fn default_value(mut self, default_value: InputValue<S>) -> Self {
         self.default_value = Some(default_value);
         self
@@ -798,35 +754,3 @@ where
 {
     <T as FromInputValue<S>>::from_input_value(v).is_some()
 }
-
-fn clean_docstring(multiline: &[&str]) -> Option<String> {
-    if multiline.is_empty() {
-        return None;
-    }
-    let trim_start = multiline
-        .iter()
-        .filter_map(|ln| ln.chars().position(|ch| !ch.is_whitespace()))
-        .min()
-        .unwrap_or(0);
-    Some(
-        multiline
-            .iter()
-            .enumerate()
-            .flat_map(|(line, ln)| {
-                let new_ln = if !ln.chars().next().map(char::is_whitespace).unwrap_or(false) {
-                    ln.trim_end() // skip trimming the first line
-                } else if ln.len() >= trim_start {
-                    ln[trim_start..].trim_end()
-                } else {
-                    ""
-                };
-                new_ln.chars().chain(
-                    ['\n']
-                        .iter()
-                        .take_while(move |_| line < multiline.len() - 1)
-                        .cloned(),
-                )
-            })
-            .collect::<String>(),
-    )
-}
diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs
index 4151127d0..395faf5ef 100644
--- a/juniper/src/types/async_await.rs
+++ b/juniper/src/types/async_await.rs
@@ -113,9 +113,15 @@ where
 
 crate::sa::assert_obj_safe!(GraphQLValueAsync<Context = (), TypeInfo = ()>);
 
+/// Helper alias for naming [trait objects][1] of [`GraphQLValueAsync`].
+///
+/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
+pub type DynGraphQLValueAsync<S, C, TI> =
+    dyn GraphQLValueAsync<S, Context = C, TypeInfo = TI> + Send + 'static;
+
 /// Extension of [`GraphQLType`] trait with asynchronous queries/mutations resolvers.
 ///
-/// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementors, so
+/// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementers, so
 /// doesn't require manual or code-generated implementation.
 pub trait GraphQLTypeAsync<S = DefaultScalarValue>: GraphQLValueAsync<S> + GraphQLType<S>
 where
diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs
index d7c77f738..46072ee69 100644
--- a/juniper/src/types/base.rs
+++ b/juniper/src/types/base.rs
@@ -290,6 +290,12 @@ where
 
 crate::sa::assert_obj_safe!(GraphQLValue<Context = (), TypeInfo = ()>);
 
+/// Helper alias for naming [trait objects][1] of [`GraphQLValue`].
+///
+/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
+pub type DynGraphQLValue<S, C, TI> =
+    dyn GraphQLValue<S, Context = C, TypeInfo = TI> + Send + Sync + 'static;
+
 /// Primary trait used to expose Rust types in a GraphQL schema.
 ///
 /// All of the convenience macros ultimately expand into an implementation of
diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs
index 792377417..4d95b7618 100644
--- a/juniper/src/types/marker.rs
+++ b/juniper/src/types/marker.rs
@@ -23,13 +23,37 @@ pub trait GraphQLObjectType<S: ScalarValue>: GraphQLType<S> {
     fn mark() {}
 }
 
+/// Maker trait for [GraphQL interfaces][1].
+///
+/// This trait extends the [`GraphQLType`] and is only used to mark an [interface][1]. During
+/// compile this addition information is required to prevent unwanted structure compiling. If an
+/// object requires this trait instead of the [`GraphQLType`], then it explicitly requires
+/// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5]
+/// and [unions][6]) are not allowed.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+/// [2]: https://spec.graphql.org/June2018/#sec-Scalars
+/// [3]: https://spec.graphql.org/June2018/#sec-Enums
+/// [4]: https://spec.graphql.org/June2018/#sec-Objects
+/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects
+/// [6]: https://spec.graphql.org/June2018/#sec-Unions
+pub trait GraphQLInterface<S: ScalarValue>: GraphQLType<S> {
+    /// An arbitrary function without meaning.
+    ///
+    /// May contain compile timed check logic which ensures that types are used correctly according
+    /// to the [GraphQL specification][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/
+    fn mark() {}
+}
+
 /// Maker trait for [GraphQL unions][1].
 ///
-/// This trait extends the [`GraphQLType`] and is only used to mark [union][1]. During compile this
-/// addition information is required to prevent unwanted structure compiling. If an object requires
-/// this trait instead of the [`GraphQLType`], then it explicitly requires [GraphQL unions][1].
-/// Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and [interfaces][6]) are
-/// not allowed.
+/// This trait extends the [`GraphQLType`] and is only used to mark an [union][1]. During compile
+/// this addition information is required to prevent unwanted structure compiling. If an object
+/// requires this trait instead of the [`GraphQLType`], then it explicitly requires
+/// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and
+/// [interfaces][6]) are not allowed.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
 /// [2]: https://spec.graphql.org/June2018/#sec-Scalars
@@ -97,7 +121,7 @@ where
 {
 }
 
-impl<'a, S, T> IsOutputType<S> for &'a [T]
+impl<S, T> IsOutputType<S> for [T]
 where
     T: IsOutputType<S>,
     S: ScalarValue,
@@ -111,7 +135,7 @@ where
 {
 }
 
-impl<'a, S, T> IsInputType<S> for &'a [T]
+impl<S, T> IsInputType<S> for [T]
 where
     T: IsInputType<S>,
     S: ScalarValue,
@@ -120,13 +144,13 @@ where
 
 impl<'a, S, T> IsInputType<S> for &T
 where
-    T: IsInputType<S>,
+    T: IsInputType<S> + ?Sized,
     S: ScalarValue,
 {
 }
 impl<'a, S, T> IsOutputType<S> for &T
 where
-    T: IsOutputType<S>,
+    T: IsOutputType<S> + ?Sized,
     S: ScalarValue,
 {
 }
@@ -144,5 +168,5 @@ where
 {
 }
 
-impl<'a, S> IsInputType<S> for &str where S: ScalarValue {}
-impl<'a, S> IsOutputType<S> for &str where S: ScalarValue {}
+impl<'a, S> IsInputType<S> for str where S: ScalarValue {}
+impl<'a, S> IsOutputType<S> for str where S: ScalarValue {}
diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs
index 70662097f..2b5981e4d 100644
--- a/juniper/src/types/subscriptions.rs
+++ b/juniper/src/types/subscriptions.rs
@@ -201,7 +201,7 @@ crate::sa::assert_obj_safe!(GraphQLSubscriptionValue<Context = (), TypeInfo = ()
 /// Extension of [`GraphQLType`] trait with asynchronous [subscription][1] execution logic.
 ///
 /// It's automatically implemented for [`GraphQLSubscriptionValue`] and [`GraphQLType`]
-/// implementors, so doesn't require manual or code-generated implementation.
+/// implementers, so doesn't require manual or code-generated implementation.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Subscription
 pub trait GraphQLSubscriptionType<S = DefaultScalarValue>:
diff --git a/juniper_actix/examples/actix_server.rs b/juniper_actix/examples/actix_server.rs
index b3b7584c3..ab32f315c 100644
--- a/juniper_actix/examples/actix_server.rs
+++ b/juniper_actix/examples/actix_server.rs
@@ -5,7 +5,7 @@ use std::env;
 use actix_cors::Cors;
 use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription, RootNode,
 };
 use juniper_actix::{
diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs
index bfb86b270..5265e8912 100644
--- a/juniper_actix/src/lib.rs
+++ b/juniper_actix/src/lib.rs
@@ -489,7 +489,7 @@ mod tests {
     use juniper::{
         futures::stream::StreamExt,
         http::tests::{run_http_test_suite, HttpIntegration, TestResponse},
-        tests::fixtures::starwars::{model::Database, schema::Query},
+        tests::fixtures::starwars::schema::{Database, Query},
         EmptyMutation, EmptySubscription, RootNode,
     };
 
@@ -666,7 +666,7 @@ mod tests {
     #[actix_web::rt::test]
     async fn batch_request_works() {
         use juniper::{
-            tests::fixtures::starwars::{model::Database, schema::Query},
+            tests::fixtures::starwars::schema::{Database, Query},
             EmptyMutation, EmptySubscription, RootNode,
         };
 
diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs
new file mode 100644
index 000000000..dc5adc139
--- /dev/null
+++ b/juniper_codegen/src/common/gen.rs
@@ -0,0 +1,47 @@
+//! Common code generated parts, used by this crate.
+
+use proc_macro2::TokenStream;
+use quote::quote;
+
+/// Generate the code resolving some [GraphQL type][1] in a synchronous manner.
+///
+/// Value of a [GraphQL type][1] should be stored in a `res` binding in the generated code, before
+/// including this piece of code.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Types
+pub(crate) fn sync_resolving_code() -> TokenStream {
+    quote! {
+        ::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()),
+            })
+    }
+}
+
+/// Generate the code resolving some [GraphQL type][1] in an asynchronous manner.
+///
+/// Value of a [GraphQL type][1] should be resolvable with `fut` binding representing a [`Future`]
+/// in the generated code, before including this piece of code.
+///
+/// Optional `ty` argument may be used to annotate a concrete type of the resolving
+/// [GraphQL type][1] (the [`Future::Output`]).
+///
+/// [`Future`]: std::future::Future
+/// [`Future::Output`]: std::future::Future::Output
+/// [1]: https://spec.graphql.org/June2018/#sec-Types
+pub(crate) fn async_resolving_code(ty: Option<&syn::Type>) -> TokenStream {
+    let ty = ty.map(|t| quote! { : #t });
+
+    quote! {
+        Box::pin(::juniper::futures::FutureExt::then(fut, move |res #ty| 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()),
+            }
+        }))
+    }
+}
diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs
new file mode 100644
index 000000000..88498c1e8
--- /dev/null
+++ b/juniper_codegen/src/common/mod.rs
@@ -0,0 +1,89 @@
+//! Common functions, definitions and extensions for code generation, used by this crate.
+
+pub(crate) mod gen;
+pub(crate) mod parse;
+
+use proc_macro2::TokenStream;
+use quote::ToTokens;
+use syn::parse_quote;
+
+/// [`ScalarValue`] parametrization of the code generation.
+///
+/// [`ScalarValue`]: juniper::ScalarValue
+#[derive(Clone, Debug)]
+pub(crate) enum ScalarValueType {
+    /// Concrete Rust type is specified as [`ScalarValue`].
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    Concrete(syn::Type),
+
+    /// One of type parameters of the original type is specified as [`ScalarValue`].
+    ///
+    /// The original type is the type that the code is generated for.
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    ExplicitGeneric(syn::Ident),
+
+    /// [`ScalarValue`] parametrization is assumed to be a generic and is not specified explicitly.
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    ImplicitGeneric,
+}
+
+impl ScalarValueType {
+    /// Indicates whether this [`ScalarValueType`] is generic.
+    #[must_use]
+    pub(crate) fn is_generic(&self) -> bool {
+        matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric)
+    }
+
+    /// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ExplicitGeneric`].
+    #[must_use]
+    pub(crate) fn is_explicit_generic(&self) -> bool {
+        matches!(self, Self::ExplicitGeneric(_))
+    }
+
+    /// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ImplicitGeneric`].
+    #[must_use]
+    pub(crate) fn is_implicit_generic(&self) -> bool {
+        matches!(self, Self::ImplicitGeneric)
+    }
+
+    /// Returns a type identifier which represents this [`ScalarValueType`].
+    #[must_use]
+    pub(crate) fn ty(&self) -> syn::Type {
+        match self {
+            Self::Concrete(ty) => ty.clone(),
+            Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param },
+            Self::ImplicitGeneric => parse_quote! { __S },
+        }
+    }
+
+    /// Returns a type parameter identifier that suits this [`ScalarValueType`].
+    #[must_use]
+    pub(crate) fn generic_ty(&self) -> syn::Type {
+        match self {
+            Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param },
+            Self::ImplicitGeneric | Self::Concrete(_) => parse_quote! { __S },
+        }
+    }
+
+    /// Returns a default [`ScalarValue`] type that is compatible with this [`ScalarValueType`].
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    #[must_use]
+    pub(crate) fn default_ty(&self) -> syn::Type {
+        match self {
+            Self::Concrete(ty) => ty.clone(),
+            Self::ExplicitGeneric(_) | Self::ImplicitGeneric => {
+                parse_quote! { ::juniper::DefaultScalarValue }
+            }
+        }
+    }
+}
+
+impl ToTokens for ScalarValueType {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.ty().to_tokens(into)
+    }
+}
diff --git a/juniper_codegen/src/common/parse/attr.rs b/juniper_codegen/src/common/parse/attr.rs
new file mode 100644
index 000000000..9e42cc975
--- /dev/null
+++ b/juniper_codegen/src/common/parse/attr.rs
@@ -0,0 +1,101 @@
+//! Common functions, definitions and extensions for parsing and modifying Rust attributes, used by
+//! this crate.
+
+use proc_macro2::{Span, TokenStream};
+use syn::parse_quote;
+
+use crate::util::path_eq_single;
+
+/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given
+/// `attr_path` and `attr_args`.
+///
+/// This function is generally used for uniting `proc_macro_attribute` with its body attributes.
+pub(crate) fn unite(
+    (attr_path, attr_args): (&str, &TokenStream),
+    attrs: &[syn::Attribute],
+) -> Vec<syn::Attribute> {
+    let mut full_attrs = Vec::with_capacity(attrs.len() + 1);
+    let attr_path = syn::Ident::new(attr_path, Span::call_site());
+    full_attrs.push(parse_quote! { #[#attr_path(#attr_args)] });
+    full_attrs.extend_from_slice(attrs);
+    full_attrs
+}
+
+/// Strips all `attr_path` attributes from the given `attrs` collection.
+///
+/// This function is generally used for removing duplicate attributes during `proc_macro_attribute`
+/// expansion, so avoid unnecessary expansion duplication.
+pub(crate) fn strip(attr_path: &str, attrs: Vec<syn::Attribute>) -> Vec<syn::Attribute> {
+    attrs
+        .into_iter()
+        .filter(|attr| !path_eq_single(&attr.path, attr_path))
+        .collect()
+}
+
+/// Common errors of parsing Rust attributes, appeared in this crate.
+pub(crate) mod err {
+    use proc_macro2::Span;
+    use syn::spanned::Spanned;
+
+    /// Creates "duplicated argument" [`syn::Error`] for the given `name` pointing to the given
+    /// `span`.
+    #[must_use]
+    pub(crate) fn dup_arg<S: AsSpan>(span: S) -> syn::Error {
+        syn::Error::new(span.as_span(), "duplicated attribute argument found")
+    }
+
+    /// Creates "unknown argument" [`syn::Error`] for the given `name` pointing to the given `span`.
+    #[must_use]
+    pub(crate) fn unknown_arg<S: AsSpan>(span: S, name: &str) -> syn::Error {
+        syn::Error::new(
+            span.as_span(),
+            format!("unknown `{}` attribute argument", name),
+        )
+    }
+
+    /// Helper coercion for [`Span`] and [`Spanned`] types to use in function arguments.
+    pub(crate) trait AsSpan {
+        /// Returns the coerced [`Span`].
+        #[must_use]
+        fn as_span(&self) -> Span;
+    }
+
+    impl AsSpan for Span {
+        #[inline]
+        fn as_span(&self) -> Self {
+            *self
+        }
+    }
+
+    impl<T: Spanned> AsSpan for &T {
+        #[inline]
+        fn as_span(&self) -> Span {
+            self.span()
+        }
+    }
+}
+
+/// Handy extension of [`Option`] methods, used in this crate.
+pub(crate) trait OptionExt {
+    type Inner;
+
+    /// Transforms the `Option<T>` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)`
+    /// to `Err(err(v))`.
+    fn none_or_else<E, F>(self, err: F) -> Result<(), E>
+    where
+        F: FnOnce(Self::Inner) -> E;
+}
+
+impl<T> OptionExt for Option<T> {
+    type Inner = T;
+
+    fn none_or_else<E, F>(self, err: F) -> Result<(), E>
+    where
+        F: FnOnce(T) -> E,
+    {
+        match self {
+            Some(v) => Err(err(v)),
+            None => Ok(()),
+        }
+    }
+}
diff --git a/juniper_codegen/src/common/parse/downcaster.rs b/juniper_codegen/src/common/parse/downcaster.rs
new file mode 100644
index 000000000..89dd67cdc
--- /dev/null
+++ b/juniper_codegen/src/common/parse/downcaster.rs
@@ -0,0 +1,94 @@
+//! Common functions, definitions and extensions for parsing downcasting functions, used by GraphQL
+//! [interfaces][1] and [unions][2] definitions to downcast its type to a concrete implementer type.
+//!
+//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+//! [2]: https://spec.graphql.org/June2018/#sec-Unions
+
+use proc_macro2::Span;
+use syn::{ext::IdentExt as _, spanned::Spanned as _};
+
+use crate::common::parse::TypeExt as _;
+
+/// Parses downcasting output type from the downcaster method return type.
+///
+/// # Errors
+///
+/// If return type is invalid (not `Option<&OutputType>`), then returns the [`Span`] to display the
+/// corresponding error at.
+pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result<syn::Type, Span> {
+    let ret_ty = match &ret_ty {
+        syn::ReturnType::Type(_, ty) => &*ty,
+        _ => return Err(ret_ty.span()),
+    };
+
+    let path = match ret_ty.unparenthesized() {
+        syn::Type::Path(syn::TypePath { qself: None, path }) => path,
+        _ => return Err(ret_ty.span()),
+    };
+
+    let (ident, args) = match path.segments.last() {
+        Some(syn::PathSegment {
+            ident,
+            arguments: syn::PathArguments::AngleBracketed(generic),
+        }) => (ident, &generic.args),
+        _ => return Err(ret_ty.span()),
+    };
+
+    if ident.unraw() != "Option" {
+        return Err(ret_ty.span());
+    }
+    if args.len() != 1 {
+        return Err(ret_ty.span());
+    }
+
+    let out_ty = match args.first() {
+        Some(syn::GenericArgument::Type(inner_ty)) => match inner_ty.unparenthesized() {
+            syn::Type::Reference(inner_ty) => {
+                if inner_ty.mutability.is_some() {
+                    return Err(inner_ty.span());
+                }
+                inner_ty.elem.unparenthesized().clone()
+            }
+            _ => return Err(ret_ty.span()),
+        },
+        _ => return Err(ret_ty.span()),
+    };
+    Ok(out_ty)
+}
+
+/// Parses context type used for downcasting from the downcaster method signature.
+///
+/// Returns [`None`] if downcaster method doesn't accept context.
+///
+/// # Errors
+///
+/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at.
+pub(crate) fn context_ty(sig: &syn::Signature) -> Result<Option<syn::Type>, Span> {
+    match sig.receiver() {
+        Some(syn::FnArg::Receiver(rcv)) => {
+            if rcv.reference.is_none() || rcv.mutability.is_some() {
+                return Err(rcv.span());
+            }
+        }
+        _ => return Err(sig.span()),
+    }
+
+    if sig.inputs.len() > 2 {
+        return Err(sig.inputs.span());
+    }
+
+    let second_arg_ty = match sig.inputs.iter().nth(1) {
+        Some(syn::FnArg::Typed(arg)) => &*arg.ty,
+        None => return Ok(None),
+        _ => return Err(sig.inputs.span()),
+    };
+    match second_arg_ty.unparenthesized() {
+        syn::Type::Reference(ref_ty) => {
+            if ref_ty.mutability.is_some() {
+                return Err(ref_ty.span());
+            }
+            Ok(Some(ref_ty.elem.unparenthesized().clone()))
+        }
+        ty => Err(ty.span()),
+    }
+}
diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs
new file mode 100644
index 000000000..fce5f0cf2
--- /dev/null
+++ b/juniper_codegen/src/common/parse/mod.rs
@@ -0,0 +1,261 @@
+//! Common functions, definitions and extensions for parsing, normalizing and modifying Rust syntax,
+//! used by this crate.
+
+pub(crate) mod attr;
+pub(crate) mod downcaster;
+
+use std::{
+    any::TypeId,
+    iter::{self, FromIterator as _},
+    mem,
+};
+
+use proc_macro2::Span;
+use syn::{
+    ext::IdentExt as _,
+    parse::{Parse, ParseBuffer},
+    parse_quote,
+    punctuated::Punctuated,
+    token::{self, Token},
+};
+
+/// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing.
+pub(crate) trait ParseBufferExt {
+    /// Tries to parse `T` as the next token.
+    ///
+    /// Doesn't move [`ParseStream`]'s cursor if there is no `T`.
+    fn try_parse<T: Default + Parse + Token>(&self) -> syn::Result<Option<T>>;
+
+    /// Checks whether next token is `T`.
+    ///
+    /// Doesn't move [`ParseStream`]'s cursor.
+    #[must_use]
+    fn is_next<T: Default + Token>(&self) -> bool;
+
+    /// Parses next token as [`syn::Ident`] _allowing_ Rust keywords, while default [`Parse`]
+    /// implementation for [`syn::Ident`] disallows keywords.
+    ///
+    /// Always moves [`ParseStream`]'s cursor.
+    fn parse_any_ident(&self) -> syn::Result<syn::Ident>;
+
+    /// Checks whether next token is a wrapper `W` and if yes, then parses the wrapped tokens as `T`
+    /// [`Punctuated`] with `P`. Otherwise, parses just `T`.
+    ///
+    /// Always moves [`ParseStream`]'s cursor.
+    fn parse_maybe_wrapped_and_punctuated<T, W, P>(&self) -> syn::Result<Punctuated<T, P>>
+    where
+        T: Parse,
+        W: Default + Token + 'static,
+        P: Default + Parse + Token;
+}
+
+impl<'a> ParseBufferExt for ParseBuffer<'a> {
+    fn try_parse<T: Default + Parse + Token>(&self) -> syn::Result<Option<T>> {
+        Ok(if self.is_next::<T>() {
+            Some(self.parse()?)
+        } else {
+            None
+        })
+    }
+
+    fn is_next<T: Default + Token>(&self) -> bool {
+        self.lookahead1().peek(|_| T::default())
+    }
+
+    fn parse_any_ident(&self) -> syn::Result<syn::Ident> {
+        self.call(syn::Ident::parse_any)
+    }
+
+    fn parse_maybe_wrapped_and_punctuated<T, W, P>(&self) -> syn::Result<Punctuated<T, P>>
+    where
+        T: Parse,
+        W: Default + Token + 'static,
+        P: Default + Parse + Token,
+    {
+        Ok(if self.is_next::<W>() {
+            let inner;
+            if TypeId::of::<W>() == TypeId::of::<token::Bracket>() {
+                let _ = syn::bracketed!(inner in self);
+            } else if TypeId::of::<W>() == TypeId::of::<token::Brace>() {
+                let _ = syn::braced!(inner in self);
+            } else if TypeId::of::<W>() == TypeId::of::<token::Paren>() {
+                let _ = syn::parenthesized!(inner in self);
+            } else {
+                unimplemented!(
+                    "ParseBufferExt::parse_maybe_wrapped_and_punctuated supports only brackets, \
+                     braces and parentheses as wrappers.",
+                );
+            }
+            Punctuated::parse_terminated(&inner)?
+        } else {
+            Punctuated::from_iter(iter::once(self.parse::<T>()?))
+        })
+    }
+}
+
+/// Extension of [`syn::Type`] providing common function widely used by this crate for parsing.
+pub(crate) trait TypeExt {
+    /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested
+    /// [`syn::TypeParen`]s asap).
+    #[must_use]
+    fn unparenthesized(&self) -> &Self;
+
+    /// Retrieves the inner [`syn::Type`] from the given reference type, or just returns "as is" if
+    /// the type is not a reference.
+    ///
+    /// Also, makes the type [`TypeExt::unparenthesized`], if possible.
+    #[must_use]
+    fn unreferenced(&self) -> &Self;
+
+    fn lifetimes_anonymized(&mut self);
+}
+
+impl TypeExt for syn::Type {
+    fn unparenthesized(&self) -> &Self {
+        match self {
+            Self::Paren(ty) => ty.elem.unparenthesized(),
+            ty => ty,
+        }
+    }
+
+    fn unreferenced(&self) -> &Self {
+        match self.unparenthesized() {
+            Self::Reference(ref_ty) => &*ref_ty.elem,
+            ty => ty,
+        }
+    }
+
+    fn lifetimes_anonymized(&mut self) {
+        use syn::{GenericArgument as GA, Type as T};
+
+        match self {
+            T::Array(syn::TypeArray { elem, .. })
+            | T::Group(syn::TypeGroup { elem, .. })
+            | T::Paren(syn::TypeParen { elem, .. })
+            | T::Ptr(syn::TypePtr { elem, .. })
+            | T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_anonymized(),
+
+            T::Tuple(syn::TypeTuple { elems, .. }) => {
+                for ty in elems.iter_mut() {
+                    ty.lifetimes_anonymized();
+                }
+            }
+
+            T::ImplTrait(syn::TypeImplTrait { bounds, .. })
+            | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
+                for bound in bounds.iter_mut() {
+                    match bound {
+                        syn::TypeParamBound::Lifetime(lt) => {
+                            lt.ident = syn::Ident::new("_", Span::call_site())
+                        }
+                        syn::TypeParamBound::Trait(_) => {
+                            todo!("Anonymizing lifetimes in trait is not yet supported")
+                        }
+                    }
+                }
+            }
+
+            T::Reference(ref_ty) => {
+                if let Some(lt) = ref_ty.lifetime.as_mut() {
+                    lt.ident = syn::Ident::new("_", Span::call_site());
+                }
+                (&mut *ref_ty.elem).lifetimes_anonymized();
+            }
+
+            T::Path(ty) => {
+                for seg in ty.path.segments.iter_mut() {
+                    match &mut seg.arguments {
+                        syn::PathArguments::AngleBracketed(angle) => {
+                            for arg in angle.args.iter_mut() {
+                                match arg {
+                                    GA::Lifetime(lt) => {
+                                        lt.ident = syn::Ident::new("_", Span::call_site());
+                                    }
+                                    GA::Type(ty) => ty.lifetimes_anonymized(),
+                                    GA::Binding(b) => b.ty.lifetimes_anonymized(),
+                                    GA::Constraint(_) | GA::Const(_) => {}
+                                }
+                            }
+                        }
+                        syn::PathArguments::Parenthesized(args) => {
+                            for ty in args.inputs.iter_mut() {
+                                ty.lifetimes_anonymized();
+                            }
+                            if let syn::ReturnType::Type(_, ty) = &mut args.output {
+                                (&mut *ty).lifetimes_anonymized();
+                            }
+                        }
+                        syn::PathArguments::None => {}
+                    }
+                }
+            }
+
+            // These types unlikely will be used as GraphQL types.
+            T::BareFn(_)
+            | T::Infer(_)
+            | T::Macro(_)
+            | T::Never(_)
+            | T::Verbatim(_)
+            | T::__Nonexhaustive => {}
+        }
+    }
+}
+
+/// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing.
+pub(crate) trait GenericsExt {
+    /// Removes all default types out of type parameters and const parameters in these
+    /// [`syn::Generics`].
+    fn remove_defaults(&mut self);
+
+    /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`].
+    fn move_bounds_to_where_clause(&mut self);
+}
+
+impl GenericsExt for syn::Generics {
+    fn remove_defaults(&mut self) {
+        use syn::GenericParam as P;
+
+        for p in &mut self.params {
+            match p {
+                P::Type(p) => {
+                    p.eq_token = None;
+                    p.default = None;
+                }
+                P::Lifetime(_) => {}
+                P::Const(p) => {
+                    p.eq_token = None;
+                    p.default = None;
+                }
+            }
+        }
+    }
+
+    fn move_bounds_to_where_clause(&mut self) {
+        use syn::GenericParam as P;
+
+        let _ = self.make_where_clause();
+        let where_clause = self.where_clause.as_mut().unwrap();
+
+        for p in &mut self.params {
+            match p {
+                P::Type(p) => {
+                    if p.colon_token.is_some() {
+                        p.colon_token = None;
+                        let bounds = mem::take(&mut p.bounds);
+                        let ty = &p.ident;
+                        where_clause.predicates.push(parse_quote! { #ty: #bounds });
+                    }
+                }
+                P::Lifetime(p) => {
+                    if p.colon_token.is_some() {
+                        p.colon_token = None;
+                        let bounds = mem::take(&mut p.bounds);
+                        let lt = &p.lifetime;
+                        where_clause.predicates.push(parse_quote! { #lt: #bounds });
+                    }
+                }
+                P::Const(_) => {}
+            }
+        }
+    }
+}
diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs
index 919d55ec0..bf6b3e7a6 100644
--- a/juniper_codegen/src/derive_enum.rs
+++ b/juniper_codegen/src/derive_enum.rs
@@ -138,7 +138,7 @@ pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<Toke
         fields,
         // NOTICE: only unit variants allow -> no generics possible
         generics: syn::Generics::default(),
-        interfaces: None,
+        interfaces: vec![],
         include_type_generics: true,
         generic_scalar: true,
         no_async: attrs.no_async.is_some(),
diff --git a/juniper_codegen/src/derive_input_object.rs b/juniper_codegen/src/derive_input_object.rs
index ab50cc0af..cc8ed3c02 100644
--- a/juniper_codegen/src/derive_input_object.rs
+++ b/juniper_codegen/src/derive_input_object.rs
@@ -138,7 +138,7 @@ pub fn impl_input_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Res
         description: attrs.description.map(SpanContainer::into_inner),
         fields,
         generics: ast.generics,
-        interfaces: None,
+        interfaces: vec![],
         include_type_generics: true,
         generic_scalar: true,
         no_async: attrs.no_async.is_some(),
diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs
index bbcc08703..de2ac2dc2 100644
--- a/juniper_codegen/src/derive_object.rs
+++ b/juniper_codegen/src/derive_object.rs
@@ -89,12 +89,6 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R
     // Early abort after checking all fields
     proc_macro_error::abort_if_dirty();
 
-    if !attrs.interfaces.is_empty() {
-        attrs.interfaces.iter().for_each(|elm| {
-            error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
-        });
-    }
-
     if let Some(duplicates) =
         crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
     {
@@ -124,7 +118,11 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R
         description: attrs.description.map(SpanContainer::into_inner),
         fields,
         generics: ast.generics,
-        interfaces: None,
+        interfaces: attrs
+            .interfaces
+            .into_iter()
+            .map(SpanContainer::into_inner)
+            .collect(),
         include_type_generics: true,
         generic_scalar: true,
         no_async: attrs.no_async.is_some(),
diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs
index 98129a625..b725cfa14 100644
--- a/juniper_codegen/src/derive_scalar_value.rs
+++ b/juniper_codegen/src/derive_scalar_value.rs
@@ -1,10 +1,11 @@
 use crate::{
+    common::parse::ParseBufferExt as _,
     result::GraphQLScope,
     util::{self, span_container::SpanContainer},
 };
 use proc_macro2::TokenStream;
 use quote::quote;
-use syn::{self, spanned::Spanned, Data, Fields, Ident, Variant};
+use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant};
 
 #[derive(Debug, Default)]
 struct TransparentAttributes {
@@ -25,12 +26,12 @@ impl syn::parse::Parse for TransparentAttributes {
             let ident: syn::Ident = input.parse()?;
             match ident.to_string().as_str() {
                 "name" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::LitStr>()?;
                     output.name = Some(val.value());
                 }
                 "description" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::LitStr>()?;
                     output.description = Some(val.value());
                 }
@@ -39,9 +40,7 @@ impl syn::parse::Parse for TransparentAttributes {
                 }
                 _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
             }
-            if input.lookahead1().peek(syn::Token![,]) {
-                input.parse::<syn::Token![,]>()?;
-            }
+            input.try_parse::<token::Comma>()?;
         }
 
         Ok(output)
diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs
new file mode 100644
index 000000000..6bda00bb6
--- /dev/null
+++ b/juniper_codegen/src/graphql_interface/attr.rs
@@ -0,0 +1,679 @@
+//! Code generation for `#[graphql_interface]` macro.
+
+use std::mem;
+
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, ToTokens as _};
+use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
+
+use crate::{
+    common::{
+        parse::{self, TypeExt as _},
+        ScalarValueType,
+    },
+    result::GraphQLScope,
+    util::{path_eq_single, span_container::SpanContainer, to_camel_case},
+};
+
+use super::{
+    inject_async_trait, ArgumentMeta, Definition, EnumType, Field, FieldArgument, ImplMeta,
+    Implementer, ImplementerDowncast, MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type,
+};
+
+/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
+const ERR: GraphQLScope = GraphQLScope::InterfaceAttr;
+
+/// Expands `#[graphql_interface]` macro into generated code.
+pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
+    if let Ok(mut ast) = syn::parse2::<syn::ItemTrait>(body.clone()) {
+        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);
+    } else if let Ok(mut ast) = syn::parse2::<syn::ItemImpl>(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]` macro placed on trait definition.
+pub fn expand_on_trait(
+    attrs: Vec<syn::Attribute>,
+    mut ast: syn::ItemTrait,
+) -> syn::Result<TokenStream> {
+    let meta = TraitMeta::from_attrs("graphql_interface", &attrs)?;
+
+    let trait_ident = &ast.ident;
+    let trait_span = ast.span();
+
+    let name = meta
+        .name
+        .clone()
+        .map(SpanContainer::into_inner)
+        .unwrap_or_else(|| trait_ident.unraw().to_string());
+    if !meta.is_internal && name.starts_with("__") {
+        ERR.no_double_underscore(
+            meta.name
+                .as_ref()
+                .map(SpanContainer::span_ident)
+                .unwrap_or_else(|| trait_ident.span()),
+        );
+    }
+
+    let scalar = meta
+        .scalar
+        .as_ref()
+        .map(|sc| {
+            ast.generics
+                .params
+                .iter()
+                .find_map(|p| {
+                    if let syn::GenericParam::Type(tp) = p {
+                        let ident = &tp.ident;
+                        let ty: syn::Type = parse_quote! { #ident };
+                        if &ty == sc.as_ref() {
+                            return Some(&tp.ident);
+                        }
+                    }
+                    None
+                })
+                .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone()))
+                .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone()))
+        })
+        .unwrap_or_else(|| ScalarValueType::ImplicitGeneric);
+
+    let mut implementers: Vec<_> = meta
+        .implementers
+        .iter()
+        .map(|ty| Implementer {
+            ty: ty.as_ref().clone(),
+            downcast: None,
+            context_ty: None,
+            scalar: scalar.clone(),
+        })
+        .collect();
+    for (ty, downcast) in &meta.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 mut fields = vec![];
+    for item in &mut ast.items {
+        if let syn::TraitItem::Method(m) = item {
+            match TraitMethod::parse(m) {
+                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_ty = d.context_ty;
+                            }
+                        }
+                        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 !all_fields_different(&fields) {
+        ERR.emit_custom(trait_span, "must have a different name for each field");
+    }
+
+    proc_macro_error::abort_if_dirty();
+
+    let context = meta
+        .context
+        .as_ref()
+        .map(|c| c.as_ref().clone())
+        .or_else(|| {
+            fields.iter().find_map(|f| {
+                f.arguments
+                    .iter()
+                    .find_map(MethodArgument::context_ty)
+                    .cloned()
+            })
+        })
+        .or_else(|| {
+            implementers
+                .iter()
+                .find_map(|impler| impler.context_ty.as_ref())
+                .cloned()
+        });
+
+    let is_trait_object = meta.r#dyn.is_some();
+
+    let is_async_trait = meta.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,
+            &meta,
+            scalar.clone(),
+            context.clone(),
+        )))
+    } else {
+        Type::Enum(Box::new(EnumType::new(
+            &ast,
+            &meta,
+            &implementers,
+            scalar.clone(),
+        )))
+    };
+
+    let generated_code = Definition {
+        ty,
+
+        name,
+        description: meta.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]` macro placed on trait implementation block.
+pub fn expand_on_impl(
+    attrs: Vec<syn::Attribute>,
+    mut ast: syn::ItemImpl,
+) -> syn::Result<TokenStream> {
+    let meta = ImplMeta::from_attrs("graphql_interface", &attrs)?;
+
+    let is_async_trait = meta.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 = meta.r#dyn.is_some();
+
+    if is_trait_object {
+        let scalar = meta
+            .scalar
+            .as_ref()
+            .map(|sc| {
+                ast.generics
+                    .params
+                    .iter()
+                    .find_map(|p| {
+                        if let syn::GenericParam::Type(tp) = p {
+                            let ident = &tp.ident;
+                            let ty: syn::Type = parse_quote! { #ident };
+                            if &ty == sc.as_ref() {
+                                return Some(&tp.ident);
+                            }
+                        }
+                        None
+                    })
+                    .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone()))
+                    .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone()))
+            })
+            .unwrap_or_else(|| ScalarValueType::ImplicitGeneric);
+
+        ast.attrs.push(parse_quote! {
+            #[allow(unused_qualifications, clippy::type_repetition_in_bounds)]
+        });
+
+        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 });
+        }
+
+        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 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,
+        );
+    }
+
+    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),
+
+    /// Method represents a custom downcasting function into the [`Implementer`] of
+    /// [GraphQL interface][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    Downcast(Box<Implementer>),
+}
+
+impl TraitMethod {
+    /// Parses this [`TraitMethod`] from the given trait method definition.
+    ///
+    /// Returns [`None`] if the trait method marked with `#[graphql_interface(ignore)]` attribute,
+    /// or parsing fails.
+    #[must_use]
+    fn parse(method: &mut syn::TraitItemMethod) -> Option<Self> {
+        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_interface"))
+            .collect();
+
+        let meta = MethodMeta::from_attrs("graphql_interface", &method_attrs)
+            .map_err(|e| proc_macro_error::emit_error!(e))
+            .ok()?;
+
+        if meta.ignore.is_some() {
+            return None;
+        }
+
+        if meta.downcast.is_some() {
+            return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?)));
+        }
+
+        Some(Self::Field(Self::parse_field(method, meta)?))
+    }
+
+    /// Parses [`TraitMethod::Downcast`] from the given trait method definition.
+    ///
+    /// Returns [`None`] if parsing fails.
+    #[must_use]
+    fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option<Implementer> {
+        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_ty,
+            scalar: ScalarValueType::ImplicitGeneric,
+        })
+    }
+
+    /// Parses [`TraitMethod::Field`] from the given trait method definition.
+    ///
+    /// Returns [`None`] if parsing fails.
+    #[must_use]
+    fn parse_field(method: &mut syn::TraitItemMethod, meta: MethodMeta) -> Option<Field> {
+        let method_ident = &method.sig.ident;
+
+        let name = meta
+            .name
+            .as_ref()
+            .map(|m| m.as_ref().value())
+            .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string()));
+        if name.starts_with("__") {
+            ERR.no_double_underscore(
+                meta.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 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);
+                        }
+                    }
+                    return err_no_method_receiver(arg);
+                }
+            };
+            args_iter
+                .filter_map(|arg| match arg {
+                    syn::FnArg::Receiver(_) => None,
+                    syn::FnArg::Typed(arg) => Self::parse_field_argument(arg),
+                })
+                .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 = meta.description.as_ref().map(|d| d.as_ref().value());
+        let deprecated = meta
+            .deprecated
+            .as_ref()
+            .map(|d| d.as_ref().as_ref().map(syn::LitStr::value));
+
+        Some(Field {
+            name,
+            ty,
+            description,
+            deprecated,
+            method: method_ident.clone(),
+            arguments,
+            is_async: method.sig.asyncness.is_some(),
+        })
+    }
+
+    /// Parses [`MethodArgument`] from the given trait method argument definition.
+    ///
+    /// Returns [`None`] if parsing fails.
+    #[must_use]
+    fn parse_field_argument(argument: &mut syn::PatType) -> Option<MethodArgument> {
+        let argument_attrs = argument.attrs.clone();
+
+        // Remove repeated attributes from the method, to omit incorrect expansion.
+        argument.attrs = mem::take(&mut argument.attrs)
+            .into_iter()
+            .filter(|attr| !path_eq_single(&attr.path, "graphql_interface"))
+            .collect();
+
+        let meta = ArgumentMeta::from_attrs("graphql_interface", &argument_attrs)
+            .map_err(|e| proc_macro_error::emit_error!(e))
+            .ok()?;
+
+        if meta.context.is_some() {
+            return Some(MethodArgument::Context(argument.ty.unreferenced().clone()));
+        }
+        if meta.executor.is_some() {
+            return Some(MethodArgument::Executor);
+        }
+        if let syn::Pat::Ident(name) = &*argument.pat {
+            let arg = match name.ident.unraw().to_string().as_str() {
+                "context" | "ctx" => {
+                    Some(MethodArgument::Context(argument.ty.unreferenced().clone()))
+                }
+                "executor" => Some(MethodArgument::Executor),
+                _ => None,
+            };
+            if arg.is_some() {
+                ensure_no_regular_field_argument_meta(&meta)?;
+                return arg;
+            }
+        }
+
+        let name = if let Some(name) = meta.name.as_ref() {
+            name.as_ref().value()
+        } else if let syn::Pat::Ident(name) = &*argument.pat {
+            to_camel_case(&name.ident.unraw().to_string())
+        } else {
+            ERR.custom(
+                argument.pat.span(),
+                "trait method argument should be declared as a single identifier",
+            )
+            .note(String::from(
+                "use `#[graphql_interface(name = ...)]` attribute to specify custom argument's \
+                 name without requiring it being a single identifier",
+            ))
+            .emit();
+            return None;
+        };
+        if name.starts_with("__") {
+            ERR.no_double_underscore(
+                meta.name
+                    .as_ref()
+                    .map(SpanContainer::span_ident)
+                    .unwrap_or_else(|| argument.pat.span()),
+            );
+            return None;
+        }
+
+        Some(MethodArgument::Regular(FieldArgument {
+            name,
+            ty: argument.ty.as_ref().clone(),
+            description: meta.description.as_ref().map(|d| d.as_ref().value()),
+            default: meta.default.as_ref().map(|v| v.as_ref().clone()),
+        }))
+    }
+}
+
+/// Checks whether the given [`ArgumentMeta`] doesn't contain arguments related to
+/// [`FieldArgument`].
+#[must_use]
+fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> {
+    if let Some(span) = &meta.name {
+        return err_disallowed_attr(&span, "name");
+    }
+    if let Some(span) = &meta.description {
+        return err_disallowed_attr(&span, "description");
+    }
+    if let Some(span) = &meta.default {
+        return err_disallowed_attr(&span, "default");
+    }
+    Some(())
+}
+
+/// Emits "argument is not allowed" [`syn::Error`] for the given `arg` pointing to the given `span`.
+#[must_use]
+fn err_disallowed_attr<T, S: Spanned>(span: &S, arg: &str) -> Option<T> {
+    ERR.custom(
+        span.span(),
+        format!(
+            "attribute argument `#[graphql_interface({} = ...)]` is not allowed here",
+            arg,
+        ),
+    )
+    .emit();
+
+    None
+}
+
+/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`.
+#[must_use]
+fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
+    ERR.custom(
+        span.span(),
+        "trait method receiver can only be a shared reference `&self`",
+    )
+    .emit();
+
+    None
+}
+
+/// Emits "no trait method receiver" [`syn::Error`] pointing to the given `span`.
+#[must_use]
+fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
+    ERR.custom(
+        span.span(),
+        "trait method should have a shared reference receiver `&self`",
+    )
+    .emit();
+
+    None
+}
+
+/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given `span`.
+fn err_only_implementer_downcast<S: Spanned>(span: &S) {
+    ERR.custom(
+        span.span(),
+        "downcasting is possible only to interface implementers",
+    )
+    .emit();
+}
+
+/// 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_interface(ignore)]` attribute argument to ignore this trait method for \
+         interface implementers downcasting",
+    ))
+    .emit()
+}
+
+/// Checks whether all [GraphQL interface][1] fields have different names.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+fn all_fields_different(fields: &[Field]) -> bool {
+    let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect();
+    names.dedup();
+    names.len() == fields.len()
+}
diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs
new file mode 100644
index 000000000..94b07e844
--- /dev/null
+++ b/juniper_codegen/src/graphql_interface/mod.rs
@@ -0,0 +1,2316 @@
+//! Code generation for [GraphQL interface][1].
+//!
+//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+
+pub mod attr;
+
+use std::collections::{HashMap, HashSet};
+
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens, TokenStreamExt as _};
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned as _,
+    token,
+};
+
+use crate::{
+    common::{
+        gen,
+        parse::{
+            attr::{err, OptionExt as _},
+            GenericsExt as _, ParseBufferExt as _,
+        },
+        ScalarValueType,
+    },
+    util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer},
+};
+
+/// Available metadata (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)]
+struct TraitMeta {
+    /// Explicitly specified name of [GraphQL interface][1] type.
+    ///
+    /// If absent, then Rust trait name is used by default.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    name: Option<SpanContainer<String>>,
+
+    /// Explicitly specified [description][2] of [GraphQL interface][1] type.
+    ///
+    /// If absent, 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
+    description: Option<SpanContainer<String>>,
+
+    /// Explicitly specified identifier of the enum Rust type behind the trait, being an actual
+    /// implementation of a [GraphQL interface][1] type.
+    ///
+    /// If absent, then `{trait_name}Value` identifier will be used.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    r#enum: Option<SpanContainer<syn::Ident>>,
+
+    /// 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 absent, 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<SpanContainer<syn::Ident>>,
+
+    /// 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<SpanContainer<syn::Type>>,
+
+    /// Explicitly specified type of [`Context`] to use for resolving this [GraphQL interface][1]
+    /// type with.
+    ///
+    /// If absent, then unit type `()` is assumed as type of [`Context`].
+    ///
+    /// [`Context`]: juniper::Context
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    context: Option<SpanContainer<syn::Type>>,
+
+    /// Explicitly specified type of [`ScalarValue`] to use for resolving this
+    /// [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 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
+    scalar: Option<SpanContainer<syn::Type>>,
+
+    /// Explicitly specified marker indicating that the Rust trait should be transformed into
+    /// [`async_trait`].
+    ///
+    /// If absent, then trait will be transformed into [`async_trait`] only if it contains async
+    /// methods.
+    asyncness: Option<SpanContainer<syn::Ident>>,
+
+    /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers.
+    ///
+    /// If absent, 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<syn::Type, SpanContainer<syn::ExprPath>>,
+
+    /// Indicator whether the generated code is intended to be used only inside the [`juniper`]
+    /// library.
+    is_internal: bool,
+}
+
+impl Parse for TraitMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let mut output = Self::default();
+
+        while !input.is_empty() {
+            let ident = input.parse_any_ident()?;
+            match ident.to_string().as_str() {
+                "name" => {
+                    input.parse::<token::Eq>()?;
+                    let name = input.parse::<syn::LitStr>()?;
+                    output
+                        .name
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(name.span()),
+                            name.value(),
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "desc" | "description" => {
+                    input.parse::<token::Eq>()?;
+                    let desc = input.parse::<syn::LitStr>()?;
+                    output
+                        .description
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(desc.span()),
+                            desc.value(),
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "ctx" | "context" | "Context" => {
+                    input.parse::<token::Eq>()?;
+                    let ctx = input.parse::<syn::Type>()?;
+                    output
+                        .context
+                        .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "scalar" | "Scalar" | "ScalarValue" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<syn::Type>()?;
+                    output
+                        .scalar
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "for" | "implementers" => {
+                    input.parse::<token::Eq>()?;
+                    for impler in input.parse_maybe_wrapped_and_punctuated::<
+                        syn::Type, token::Bracket, token::Comma,
+                    >()? {
+                        let impler_span = impler.span();
+                        output
+                            .implementers
+                            .replace(SpanContainer::new(ident.span(), Some(impler_span), impler))
+                            .none_or_else(|_| err::dup_arg(impler_span))?;
+                    }
+                }
+                "dyn" => {
+                    input.parse::<token::Eq>()?;
+                    let alias = input.parse::<syn::Ident>()?;
+                    output
+                        .r#dyn
+                        .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "enum" => {
+                    input.parse::<token::Eq>()?;
+                    let alias = input.parse::<syn::Ident>()?;
+                    output
+                        .r#enum
+                        .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "async" => {
+                    let span = ident.span();
+                    output
+                        .asyncness
+                        .replace(SpanContainer::new(span, Some(span), ident))
+                        .none_or_else(|_| err::dup_arg(span))?;
+                }
+                "on" => {
+                    let ty = input.parse::<syn::Type>()?;
+                    input.parse::<token::Eq>()?;
+                    let dwncst = input.parse::<syn::ExprPath>()?;
+                    let dwncst_spanned = SpanContainer::new(ident.span(), Some(ty.span()), dwncst);
+                    let dwncst_span = dwncst_spanned.span_joined();
+                    output
+                        .external_downcasts
+                        .insert(ty, dwncst_spanned)
+                        .none_or_else(|_| err::dup_arg(dwncst_span))?
+                }
+                "internal" => {
+                    output.is_internal = true;
+                }
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
+                }
+            }
+            input.try_parse::<token::Comma>()?;
+        }
+
+        Ok(output)
+    }
+}
+
+impl TraitMeta {
+    /// Tries to merge two [`TraitMeta`]s into a single one, reporting about duplicates, if any.
+    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
+        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#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
+            ),
+            is_internal: self.is_internal || another.is_internal,
+        })
+    }
+
+    /// Parses [`TraitMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait
+    /// definition.
+    fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let mut meta = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if let Some(as_dyn) = &meta.r#dyn {
+            if meta.r#enum.is_some() {
+                return Err(syn::Error::new(
+                    as_dyn.span(),
+                    "`dyn` attribute argument is not composable with `enum` attribute argument",
+                ));
+            }
+        }
+
+        if meta.description.is_none() {
+            meta.description = get_doc_comment(attrs);
+        }
+
+        Ok(meta)
+    }
+}
+
+/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait
+/// implementation block, when generating code for [GraphQL interface][1] type.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+#[derive(Debug, Default)]
+struct ImplMeta {
+    /// Explicitly specified type of [`ScalarValue`] to use for implementing the
+    /// [GraphQL interface][1] type.
+    ///
+    /// 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.
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    scalar: Option<SpanContainer<syn::Type>>,
+
+    /// 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.
+    ///
+    /// This marker is especially useful when Rust trait contains async default methods, while the
+    /// implementation block doesn't.
+    asyncness: Option<SpanContainer<syn::Ident>>,
+
+    /// 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).
+    ///
+    /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html
+    r#dyn: Option<SpanContainer<syn::Ident>>,
+}
+
+impl Parse for ImplMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let mut output = Self::default();
+
+        while !input.is_empty() {
+            let ident = input.parse_any_ident()?;
+            match ident.to_string().as_str() {
+                "scalar" | "Scalar" | "ScalarValue" => {
+                    input.parse::<token::Eq>()?;
+                    let scl = input.parse::<syn::Type>()?;
+                    output
+                        .scalar
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "dyn" => {
+                    let span = ident.span();
+                    output
+                        .r#dyn
+                        .replace(SpanContainer::new(span, Some(span), ident))
+                        .none_or_else(|_| err::dup_arg(span))?;
+                }
+                "async" => {
+                    let span = ident.span();
+                    output
+                        .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::<token::Comma>()?;
+        }
+
+        Ok(output)
+    }
+}
+
+impl ImplMeta {
+    /// Tries to merge two [`ImplMeta`]s into a single one, reporting about duplicates, if any.
+    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
+        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 [`ImplMeta`] 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<Self> {
+        filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
+    }
+}
+
+/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait method
+/// definition, when generating code for [GraphQL interface][1] type.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+#[derive(Debug, Default)]
+struct MethodMeta {
+    /// Explicitly specified name of a [GraphQL field][1] represented by this trait method.
+    ///
+    /// If absent, then `camelCased` Rust method name is used by default.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    name: Option<SpanContainer<syn::LitStr>>,
+
+    /// Explicitly specified [description][2] of this [GraphQL field][1].
+    ///
+    /// If absent, then Rust doc comment is used as the [description][2], if any.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
+    description: Option<SpanContainer<syn::LitStr>>,
+
+    /// Explicitly specified [deprecation][2] of this [GraphQL field][1].
+    ///
+    /// If absent, then Rust `#[deprecated]` attribute is used as the [deprecation][2], if any.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation
+    deprecated: Option<SpanContainer<Option<syn::LitStr>>>,
+
+    /// Explicitly specified marker indicating that this trait method should be omitted by code
+    /// generation and not considered in the [GraphQL interface][1] type definition.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    ignore: Option<SpanContainer<syn::Ident>>,
+
+    /// 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 [`TraitMeta::external_downcasts`] function.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Objects
+    downcast: Option<SpanContainer<syn::Ident>>,
+}
+
+impl Parse for MethodMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let mut output = Self::default();
+
+        while !input.is_empty() {
+            let ident = input.parse::<syn::Ident>()?;
+            match ident.to_string().as_str() {
+                "name" => {
+                    input.parse::<token::Eq>()?;
+                    let name = input.parse::<syn::LitStr>()?;
+                    output
+                        .name
+                        .replace(SpanContainer::new(ident.span(), Some(name.span()), name))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "desc" | "description" => {
+                    input.parse::<token::Eq>()?;
+                    let desc = input.parse::<syn::LitStr>()?;
+                    output
+                        .description
+                        .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "deprecated" => {
+                    let mut reason = None;
+                    if input.is_next::<token::Eq>() {
+                        input.parse::<token::Eq>()?;
+                        reason = Some(input.parse::<syn::LitStr>()?);
+                    }
+                    output
+                        .deprecated
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            reason.as_ref().map(|r| r.span()),
+                            reason,
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "ignore" | "skip" => output
+                    .ignore
+                    .replace(SpanContainer::new(ident.span(), None, ident.clone()))
+                    .none_or_else(|_| err::dup_arg(&ident))?,
+                "downcast" => output
+                    .downcast
+                    .replace(SpanContainer::new(ident.span(), None, ident.clone()))
+                    .none_or_else(|_| err::dup_arg(&ident))?,
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
+                }
+            }
+            input.try_parse::<token::Comma>()?;
+        }
+
+        Ok(output)
+    }
+}
+
+impl MethodMeta {
+    /// Tries to merge two [`MethodMeta`]s into a single one, reporting about duplicates, if any.
+    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
+        Ok(Self {
+            name: try_merge_opt!(name: self, another),
+            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),
+        })
+    }
+
+    /// Parses [`MethodMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a
+    /// method definition.
+    pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let mut meta = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if let Some(ignore) = &meta.ignore {
+            if meta.name.is_some()
+                || meta.description.is_some()
+                || meta.deprecated.is_some()
+                || meta.downcast.is_some()
+            {
+                return Err(syn::Error::new(
+                    ignore.span(),
+                    "`ignore` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        if let Some(downcast) = &meta.downcast {
+            if meta.name.is_some()
+                || meta.description.is_some()
+                || meta.deprecated.is_some()
+                || meta.ignore.is_some()
+            {
+                return Err(syn::Error::new(
+                    downcast.span(),
+                    "`downcast` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        if meta.description.is_none() {
+            meta.description = get_doc_comment(attrs).map(|sc| {
+                let span = sc.span_ident();
+                sc.map(|desc| syn::LitStr::new(&desc, span))
+            });
+        }
+
+        if meta.deprecated.is_none() {
+            meta.deprecated = get_deprecated(attrs).map(|sc| {
+                let span = sc.span_ident();
+                sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span)))
+            });
+        }
+
+        Ok(meta)
+    }
+}
+
+/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait method
+/// argument, when generating code for [GraphQL interface][1] type.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+#[derive(Debug, Default)]
+struct ArgumentMeta {
+    /// Explicitly specified name of a [GraphQL argument][1] represented by this method argument.
+    ///
+    /// If absent, then `camelCased` Rust argument name is used by default.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    name: Option<SpanContainer<syn::LitStr>>,
+
+    /// Explicitly specified [description][2] of this [GraphQL argument][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
+    description: Option<SpanContainer<syn::LitStr>>,
+
+    /// Explicitly specified [default value][2] of this [GraphQL argument][1].
+    ///
+    /// If the exact default expression is not specified, then the [`Default::default`] value is
+    /// used.
+    ///
+    /// If absent, then this [GraphQL argument][1] is considered as [required][2].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments
+    default: Option<SpanContainer<Option<syn::Expr>>>,
+
+    /// Explicitly specified marker indicating that this method argument doesn't represent a
+    /// [GraphQL argument][1], but is a [`Context`] being injected into a [GraphQL field][2]
+    /// resolving function.
+    ///
+    /// If absent, then the method argument still is considered as [`Context`] if it's named
+    /// `context` or `ctx`.
+    ///
+    /// [`Context`]: juniper::Context
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    context: Option<SpanContainer<syn::Ident>>,
+
+    /// Explicitly specified marker indicating that this method argument doesn't represent a
+    /// [GraphQL argument][1], but is a [`Executor`] being injected into a [GraphQL field][2]
+    /// resolving function.
+    ///
+    /// If absent, then the method argument still is considered as [`Context`] if it's named
+    /// `executor`.
+    ///
+    /// [`Executor`]: juniper::Executor
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    executor: Option<SpanContainer<syn::Ident>>,
+}
+
+impl Parse for ArgumentMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let mut output = Self::default();
+
+        while !input.is_empty() {
+            let ident = input.parse::<syn::Ident>()?;
+            match ident.to_string().as_str() {
+                "name" => {
+                    input.parse::<token::Eq>()?;
+                    let name = input.parse::<syn::LitStr>()?;
+                    output
+                        .name
+                        .replace(SpanContainer::new(ident.span(), Some(name.span()), name))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "desc" | "description" => {
+                    input.parse::<token::Eq>()?;
+                    let desc = input.parse::<syn::LitStr>()?;
+                    output
+                        .description
+                        .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "default" => {
+                    let mut expr = None;
+                    if input.is_next::<token::Eq>() {
+                        input.parse::<token::Eq>()?;
+                        expr = Some(input.parse::<syn::Expr>()?);
+                    } else if input.is_next::<token::Paren>() {
+                        let inner;
+                        let _ = syn::parenthesized!(inner in input);
+                        expr = Some(inner.parse::<syn::Expr>()?);
+                    }
+                    output
+                        .default
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            expr.as_ref().map(|e| e.span()),
+                            expr,
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "ctx" | "context" | "Context" => {
+                    let span = ident.span();
+                    output
+                        .context
+                        .replace(SpanContainer::new(span, Some(span), ident))
+                        .none_or_else(|_| err::dup_arg(span))?
+                }
+                "exec" | "executor" => {
+                    let span = ident.span();
+                    output
+                        .executor
+                        .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::<token::Comma>()?;
+        }
+
+        Ok(output)
+    }
+}
+
+impl ArgumentMeta {
+    /// Tries to merge two [`ArgumentMeta`]s into a single one, reporting about duplicates, if any.
+    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
+        Ok(Self {
+            name: try_merge_opt!(name: self, another),
+            description: try_merge_opt!(description: self, another),
+            default: try_merge_opt!(default: self, another),
+            context: try_merge_opt!(context: self, another),
+            executor: try_merge_opt!(executor: self, another),
+        })
+    }
+
+    /// Parses [`ArgumentMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a
+    /// function argument.
+    fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let meta = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if let Some(context) = &meta.context {
+            if meta.name.is_some()
+                || meta.description.is_some()
+                || meta.default.is_some()
+                || meta.executor.is_some()
+            {
+                return Err(syn::Error::new(
+                    context.span(),
+                    "`context` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        if let Some(executor) = &meta.executor {
+            if meta.name.is_some()
+                || meta.description.is_some()
+                || meta.default.is_some()
+                || meta.context.is_some()
+            {
+                return Err(syn::Error::new(
+                    executor.span(),
+                    "`executor` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        Ok(meta)
+    }
+}
+
+/// 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.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    ty: Type,
+
+    /// Name of this [GraphQL interface][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    name: String,
+
+    /// Description of this [GraphQL interface][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    description: Option<String>,
+
+    /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with for this
+    /// [GraphQL interface][1].
+    ///
+    /// If [`None`] then generated code will use unit type `()` as [`Context`].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`Context`]: juniper::Context
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    context: Option<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
+    scalar: ScalarValueType,
+
+    /// Defined [`Field`]s of this [GraphQL interface][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    fields: Vec<Field>,
+
+    /// Defined [`Implementer`]s of this [GraphQL interface][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    implementers: Vec<Implementer>,
+}
+
+impl Definition {
+    /// Returns generated code that panics about unknown field tried to be resolved on this
+    /// [GraphQL interface][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    #[must_use]
+    fn panic_no_field_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        quote! {
+            panic!(
+                "Field `{}` not found on type `{}`",
+                field,
+                <Self as ::juniper::GraphQLType<#scalar>>::name(info).unwrap(),
+            )
+        }
+    }
+
+    /// 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.ty.impl_generics();
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+        let ty = self.ty.ty_tokens();
+
+        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: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).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(Field::method_meta_tokens);
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #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 generics = self.ty.impl_generics();
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+        let ty = self.ty.ty_tokens();
+        let trait_ty = self.ty.trait_ty();
+        let context = self.context.clone().unwrap_or_else(|| parse_quote! { () });
+
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .filter_map(|f| f.method_resolve_field_tokens(&trait_ty));
+        let async_fields_panic = {
+            let names = self
+                .fields
+                .iter()
+                .filter_map(|field| {
+                    if field.is_async {
+                        Some(&field.name)
+                    } else {
+                        None
+                    }
+                })
+                .collect::<Vec<_>>();
+            if names.is_empty() {
+                None
+            } else {
+                Some(quote! {
+                    #( #names )|* => panic!(
+                        "Tried to resolve async field `{}` on type `{}` with a sync resolver",
+                        field,
+                        <Self as ::juniper::GraphQLType<#scalar>>::name(info).unwrap(),
+                    ),
+                })
+            }
+        };
+        let no_field_panic = self.panic_no_field_tokens();
+
+        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 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();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause
+            {
+                type Context = #context;
+                type TypeInfo = ();
+
+                fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
+                    <Self as ::juniper::GraphQLType<#scalar>>::name(info)
+                }
+
+                fn resolve_field(
+                    &self,
+                    info: &Self::TypeInfo,
+                    field: &str,
+                    args: &::juniper::Arguments<#scalar>,
+                    executor: &::juniper::Executor<Self::Context, #scalar>,
+                ) -> ::juniper::ExecutionResult<#scalar> {
+                    match field {
+                        #( #fields_resolvers )*
+                        #async_fields_panic
+                        _ => #no_field_panic,
+                    }
+                }
+
+                fn concrete_type_name(
+                    &self,
+                    context: &Self::Context,
+                    info: &Self::TypeInfo,
+                ) -> String {
+                    #( #custom_downcast_checks )*
+                    #regular_downcast_check
+                }
+
+                fn resolve_into_type(
+                    &self,
+                    info: &Self::TypeInfo,
+                    type_name: &str,
+                    _: Option<&[::juniper::Selection<#scalar>]>,
+                    executor: &::juniper::Executor<Self::Context, #scalar>,
+                ) -> ::juniper::ExecutionResult<#scalar> {
+                    #( #custom_downcasts )*
+                    #regular_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.ty.impl_generics();
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let mut where_clause = where_clause
+            .cloned()
+            .unwrap_or_else(|| parse_quote! { where });
+        where_clause.predicates.push(parse_quote! { Self: Sync });
+        if self.scalar.is_generic() {
+            where_clause
+                .predicates
+                .push(parse_quote! { #scalar: Send + Sync });
+        }
+
+        let ty = self.ty.ty_tokens();
+        let trait_ty = self.ty.trait_ty();
+
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .map(|f| f.method_resolve_field_async_tokens(&trait_ty));
+        let no_field_panic = self.panic_no_field_tokens();
+
+        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();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #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<Self::Context, #scalar>,
+                ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
+                    match field {
+                        #( #fields_resolvers )*
+                        _ => #no_field_panic,
+                    }
+                }
+
+                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>> {
+                    #( #custom_downcasts )*
+                    #regular_downcast
+                }
+            }
+        }
+    }
+
+    /// 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 generics = self.ty.impl_generics();
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+        let ty = self.ty.ty_tokens();
+
+        let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect();
+
+        let all_implers_unique = if impler_tys.len() > 1 {
+            Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); })
+        } else {
+            None
+        };
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause
+            {
+                fn mark() {
+                    #all_implers_unique
+
+                    #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#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.ty.impl_generics();
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+        let ty = self.ty.ty_tokens();
+
+        let fields_marks = self.fields.iter().map(|field| {
+            let arguments_marks = field.arguments.iter().filter_map(|arg| {
+                let arg_ty = &arg.as_regular()?.ty;
+                Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); })
+            });
+
+            let field_ty = &field.ty;
+            let resolved_ty = quote! {
+                <#field_ty as ::juniper::IntoResolvable<
+                    '_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
+                >>::Type
+            };
+
+            quote! {
+                #( #arguments_marks )*
+                <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
+            }
+        });
+
+        let impler_tys = self.implementers.iter().map(|impler| &impler.ty);
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause
+            {
+                fn mark() {
+                    #( #fields_marks )*
+                    #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
+                }
+            }
+        }
+    }
+}
+
+impl ToTokens for Definition {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        into.append_all(&[
+            self.ty.to_token_stream(),
+            self.impl_graphql_interface_tokens(),
+            self.impl_output_type_tokens(),
+            self.impl_graphql_type_tokens(),
+            self.impl_graphql_value_tokens(),
+            self.impl_graphql_value_async_tokens(),
+        ]);
+    }
+}
+
+/// Representation of [GraphQL interface][1] field [argument][2] for code generation.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+/// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+#[derive(Debug)]
+struct FieldArgument {
+    /// Rust type that this [GraphQL field argument][2] is represented by.
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    ty: syn::Type,
+
+    /// Name of this [GraphQL field argument][2] in GraphQL schema.
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    name: String,
+
+    /// [Description][1] of this [GraphQL field argument][2] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    description: Option<String>,
+
+    /// Default value of this [GraphQL field argument][2] in GraphQL schema.
+    ///
+    /// If outer [`Option`] is [`None`], then this [argument][2] is a [required][3] one.
+    ///
+    /// If inner [`Option`] is [`None`], then the [`Default::default`] value is used.
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [3]: https://spec.graphql.org/June2018/#sec-Required-Arguments
+    default: Option<Option<syn::Expr>>,
+}
+
+/// Possible kinds of Rust trait method arguments for code generation.
+#[derive(Debug)]
+enum MethodArgument {
+    /// Regular [GraphQL field argument][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    Regular(FieldArgument),
+
+    /// [`Context`] passed into a [GraphQL field][2] resolving method.
+    ///
+    /// [`Context`]: juniper::Context
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    Context(syn::Type),
+
+    /// [`Executor`] passed into a [GraphQL field][2] resolving method.
+    ///
+    /// [`Executor`]: juniper::Executor
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    Executor,
+}
+
+impl MethodArgument {
+    /// Returns this [`MethodArgument`] as a [`FieldArgument`], if it represents one.
+    #[must_use]
+    fn as_regular(&self) -> Option<&FieldArgument> {
+        if let Self::Regular(arg) = self {
+            Some(arg)
+        } else {
+            None
+        }
+    }
+
+    /// Returns [`syn::Type`] of this [`MethodArgument::Context`], if it represents one.
+    #[must_use]
+    fn context_ty(&self) -> Option<&syn::Type> {
+        if let Self::Context(ty) = self {
+            Some(ty)
+        } else {
+            None
+        }
+    }
+
+    /// Returns generated code for the [`GraphQLType::meta`] method, which registers this
+    /// [`MethodArgument`] in [`Registry`], if it represents a [`FieldArgument`].
+    ///
+    /// [`GraphQLType::meta`]: juniper::GraphQLType::meta
+    /// [`Registry`]: juniper::Registry
+    #[must_use]
+    fn method_meta_tokens(&self) -> Option<TokenStream> {
+        let arg = self.as_regular()?;
+
+        let (name, ty) = (&arg.name, &arg.ty);
+
+        let description = arg
+            .description
+            .as_ref()
+            .map(|desc| quote! { .description(#desc) });
+
+        let method = if let Some(val) = &arg.default {
+            let val = val
+                .as_ref()
+                .map(|v| quote! { (#v).into() })
+                .unwrap_or_else(|| quote! { <#ty as Default>::default() });
+            quote! { .arg_with_default::<#ty>(#name, &#val, info) }
+        } else {
+            quote! { .arg::<#ty>(#name, info) }
+        };
+
+        Some(quote! { .argument(registry#method#description) })
+    }
+
+    /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which provides the
+    /// value of this [`MethodArgument`] to be passed into a trait method call.
+    ///
+    /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
+    #[must_use]
+    fn method_resolve_field_tokens(&self) -> TokenStream {
+        match self {
+            Self::Regular(arg) => {
+                let (name, ty) = (&arg.name, &arg.ty);
+                let err_text = format!(
+                    "Internal error: missing argument `{}` - validation must have failed",
+                    &name,
+                );
+
+                quote! {
+                    args.get::<#ty>(#name).expect(#err_text)
+                }
+            }
+
+            Self::Context(_) => quote! {
+                ::juniper::FromContext::from(executor.context())
+            },
+
+            Self::Executor => quote! { &executor },
+        }
+    }
+}
+
+/// Representation of [GraphQL interface][1] [field][2] for code generation.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+/// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+#[derive(Debug)]
+struct Field {
+    /// Rust type that this [GraphQL field][2] is represented by (method return type).
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    ty: syn::Type,
+
+    /// Name of this [GraphQL field][2] in GraphQL schema.
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    name: String,
+
+    /// [Description][1] of this [GraphQL field][2] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    description: Option<String>,
+
+    /// [Deprecation][1] of this [GraphQL field][2] to put into GraphQL schema.
+    ///
+    /// If inner [`Option`] is [`None`], then deprecation has no message attached.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Deprecation
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    deprecated: Option<Option<String>>,
+
+    /// Name of Rust trait method representing this [GraphQL field][2].
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    method: syn::Ident,
+
+    /// Rust trait [`MethodArgument`]s required to call the trait method representing this
+    /// [GraphQL field][2].
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    arguments: Vec<MethodArgument>,
+
+    /// Indicator whether this [GraphQL field][2] should be resolved asynchronously.
+    ///
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    is_async: bool,
+}
+
+impl Field {
+    /// Returns generated code for the [`GraphQLType::meta`] method, which registers this
+    /// [`Field`] in [`Registry`].
+    ///
+    /// [`GraphQLType::meta`]: juniper::GraphQLType::meta
+    /// [`Registry`]: juniper::Registry
+    #[must_use]
+    fn method_meta_tokens(&self) -> TokenStream {
+        let (name, ty) = (&self.name, &self.ty);
+
+        let description = self
+            .description
+            .as_ref()
+            .map(|desc| quote! { .description(#desc) });
+
+        let deprecated = self.deprecated.as_ref().map(|reason| {
+            let reason = reason
+                .as_ref()
+                .map(|rsn| quote! { Some(#rsn) })
+                .unwrap_or_else(|| quote! { None });
+            quote! { .deprecated(#reason) }
+        });
+
+        let arguments = self
+            .arguments
+            .iter()
+            .filter_map(MethodArgument::method_meta_tokens);
+
+        quote! {
+            registry.field_convert::<#ty, _, Self::Context>(#name, info)
+                #( #arguments )*
+                #description
+                #deprecated
+        }
+    }
+
+    /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this
+    /// [`Field`] synchronously.
+    ///
+    /// Returns [`None`] if this [`Field::is_async`].
+    ///
+    /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
+    #[must_use]
+    fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option<TokenStream> {
+        if self.is_async {
+            return None;
+        }
+
+        let (name, ty, method) = (&self.name, &self.ty, &self.method);
+
+        let arguments = self
+            .arguments
+            .iter()
+            .map(MethodArgument::method_resolve_field_tokens);
+
+        let resolving_code = gen::sync_resolving_code();
+
+        Some(quote! {
+            #name => {
+                let res: #ty = <Self as #trait_ty>::#method(self #( , #arguments )*);
+                #resolving_code
+            }
+        })
+    }
+
+    /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which
+    /// resolves this [`Field`] asynchronously.
+    ///
+    /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async
+    #[must_use]
+    fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream {
+        let (name, ty, method) = (&self.name, &self.ty, &self.method);
+
+        let arguments = self
+            .arguments
+            .iter()
+            .map(MethodArgument::method_resolve_field_tokens);
+
+        let mut fut = quote! { <Self as #trait_ty>::#method(self #( , #arguments )*) };
+        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
+            }
+        }
+    }
+}
+
+/// 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 [GraphQL interface][1].
+    ///
+    /// [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,
+    },
+
+    /// Downcast is performed via some external function.
+    External {
+        /// Path of the external function to be called with.
+        path: syn::ExprPath,
+    },
+}
+
+/// 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,
+
+    /// Custom [`ImplementerDowncast`] for this [`Implementer`].
+    ///
+    /// If absent, then [`Implementer`] is downcast from an enum variant or a trait object.
+    downcast: Option<ImplementerDowncast>,
+
+    /// 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.
+    ///
+    /// [`Context`]: juniper::Context
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    context_ty: Option<syn::Type>,
+
+    /// [`ScalarValue`] parametrization of this [`Implementer`].
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    scalar: ScalarValueType,
+}
+
+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<syn::Expr>,
+    ) -> Option<TokenStream> {
+        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;
+                }
+                quote! { <Self as #trait_ty>::#name }
+            }
+            ImplementerDowncast::External { path } => {
+                quote! { #path }
+            }
+        };
+
+        Some(quote! {
+            #fn_path(self #ctx_arg)
+        })
+    }
+
+    /// 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`].
+    ///
+    /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name
+    #[must_use]
+    fn method_concrete_type_name_tokens(&self, trait_ty: &syn::Type) -> Option<TokenStream> {
+        self.downcast.as_ref()?;
+
+        let ty = &self.ty;
+        let scalar = &self.scalar;
+
+        let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context }));
+
+        // 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();
+            }
+        })
+    }
+
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts
+    /// the [GraphQL interface][1] type into this [`Implementer`] synchronously.
+    ///
+    /// Returns [`None`] if there is no custom [`Implementer::downcast`].
+    ///
+    /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    #[must_use]
+    fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option<TokenStream> {
+        self.downcast.as_ref()?;
+
+        let ty = &self.ty;
+        let scalar = &self.scalar;
+
+        let downcast = self.downcast_call_tokens(trait_ty, None);
+
+        let resolving_code = gen::sync_resolving_code();
+
+        Some(quote! {
+            if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
+                let res = #downcast;
+                return #resolving_code;
+            }
+        })
+    }
+
+    /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method,
+    /// which downcasts the [GraphQL interface][1] type into this [`Implementer`] 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<TokenStream> {
+        self.downcast.as_ref()?;
+
+        let ty = &self.ty;
+        let scalar = &self.scalar;
+
+        let downcast = self.downcast_call_tokens(trait_ty, None);
+
+        let resolving_code = gen::async_resolving_code(None);
+
+        Some(quote! {
+            if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
+                let fut = ::juniper::futures::future::ready(#downcast);
+                return #resolving_code;
+            }
+        })
+    }
+}
+
+/// 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<syn::Type>,
+
+    /// 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<syn::Signature>,
+
+    /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this
+    /// [`EnumType`].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    scalar: ScalarValueType,
+}
+
+impl EnumType {
+    /// Constructs new [`EnumType`] out of the given parameters.
+    #[must_use]
+    fn new(
+        r#trait: &syn::ItemTrait,
+        meta: &TraitMeta,
+        implers: &[Implementer],
+        scalar: ScalarValueType,
+    ) -> 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 positional `num` in the
+    /// enum type definition.
+    #[must_use]
+    fn variant_ident(num: usize) -> syn::Ident {
+        format_ident!("Impl{}", num)
+    }
+
+    /// 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<TokenStream> {
+        if self.has_phantom_variant() || self.variants.is_empty() {
+            Some(quote! { _ => unreachable!(), })
+        } else {
+            None
+        }
+    }
+
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation
+    /// for this [`EnumType`].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    #[must_use]
+    fn impl_generics(&self) -> syn::Generics {
+        let mut generics = self.trait_generics.clone();
+
+        let scalar = &self.scalar;
+        if self.scalar.is_implicit_generic() {
+            generics.params.push(parse_quote! { #scalar });
+        }
+        if self.scalar.is_generic() {
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
+        }
+
+        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().enumerate().map(|(n, ty)| {
+            let variant = Self::variant_ident(n);
+
+            quote! { #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 = &lt.lifetime;
+                        quote! { &#lifetime () }
+                    }
+                    syn::GenericParam::Const(_) => unimplemented!(),
+                };
+                quote! {
+                    ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr<Box<#ty>>>
+                }
+            });
+
+            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<Item = TokenStream> + '_ {
+        let enum_ty = &self.ident;
+        let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl();
+
+        self.variants.iter().enumerate().map(move |(n, ty)| {
+            let variant = Self::variant_ident(n);
+
+            quote! {
+                #[automatically_derived]
+                impl#impl_generics From<#ty> for #enum_ty#generics #where_clause {
+                    fn from(v: #ty) -> Self {
+                        Self::#variant(v)
+                    }
+                }
+            }
+        })
+    }
+
+    /// 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().enumerate().map(|(n, ty)| {
+                let variant = Self::variant_ident(n);
+                let args = args.clone();
+
+                quote! {
+                    Self::#variant(v) =>
+                        <#ty as #trait_ident#generics>::#method(v #( , #args )* )#and_await,
+                }
+            });
+            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! {
+            #[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`] method, which returns
+    /// name of the underlying [`Implementer`] GraphQL type contained in this [`EnumType`].
+    ///
+    /// [`GraphQLValue::concrete_type_name`]: 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().enumerate().map(|(n, ty)| {
+            let variant = Self::variant_ident(n);
+
+            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 {
+                #( #match_arms )*
+                #non_exhaustive_match_arm
+            }
+        }
+    }
+
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts
+    /// this [`EnumType`] into its underlying [`Implementer`] type synchronously.
+    ///
+    /// [`GraphQLValue::resolve_into_type`]: 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.variants.iter().enumerate().map(|(n, _)| {
+            let variant = Self::variant_ident(n);
+
+            quote! {
+                Self::#variant(res) => #resolving_code,
+            }
+        });
+        let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens();
+
+        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.variants.iter().enumerate().map(|(n, _)| {
+            let variant = Self::variant_ident(n);
+
+            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 ToTokens for EnumType {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        into.append_all(&[self.type_definition_tokens()]);
+        into.append_all(self.impl_from_tokens());
+        into.append_all(&[self.impl_trait_tokens()]);
+    }
+}
+
+/// 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: ScalarValueType,
+
+    /// Rust type of [`Context`] to generate this [`TraitObjectType`] with.
+    ///
+    /// If [`None`] then generated code will use unit type `()` as [`Context`].
+    ///
+    /// [`Context`]: juniper::Context
+    context: Option<syn::Type>,
+}
+
+impl TraitObjectType {
+    /// Constructs new [`TraitObjectType`] out of the given parameters.
+    #[must_use]
+    fn new(
+        r#trait: &syn::ItemTrait,
+        meta: &TraitMeta,
+        scalar: ScalarValueType,
+        context: Option<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,
+        }
+    }
+
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation
+    /// for this [`TraitObjectType`].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    #[must_use]
+    fn impl_generics(&self) -> 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 });
+        }
+        if scalar.is_generic() {
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
+        }
+
+        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.clone().unwrap_or_else(|| parse_quote! { () });
+
+        quote! {
+            dyn #ty<#ty_params, Context = #context, TypeInfo = ()> + '__obj + Send + Sync
+        }
+    }
+
+    /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns
+    /// name of the underlying [`Implementer`] GraphQL type contained in this [`TraitObjectType`].
+    ///
+    /// [`GraphQLValue::concrete_type_name`]: 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`] method, which downcasts
+    /// this [`TraitObjectType`] into its underlying [`Implementer`] type synchronously.
+    ///
+    /// [`GraphQLValue::resolve_into_type`]: 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.clone().unwrap_or_else(|| parse_quote! { () });
+
+        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<EnumType>),
+
+    /// [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<TraitObjectType>),
+}
+
+impl Type {
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation
+    /// for this [`Type`].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    #[must_use]
+    fn impl_generics(&self) -> syn::Generics {
+        match self {
+            Self::Enum(e) => e.impl_generics(),
+            Self::TraitObject(o) => o.impl_generics(),
+        }
+    }
+
+    /// 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`] method, which returns
+    /// name of the underlying [`Implementer`] GraphQL type contained in this [`Type`].
+    ///
+    /// [`GraphQLValue::concrete_type_name`]: 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`] method, which downcasts
+    /// this [`Type`] into its underlying [`Implementer`] type synchronously.
+    ///
+    /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type
+    #[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(),
+        }
+    }
+}
+
+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),
+        }
+    }
+}
+
+/// 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<syn::Attribute>, methods: M, generics: &syn::Generics)
+where
+    M: IntoIterator<Item = &'m mut syn::Signature>,
+{
+    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/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs
index 1cdea3e3c..c7b29ed24 100644
--- a/juniper_codegen/src/graphql_union/attr.rs
+++ b/juniper_codegen/src/graphql_union/attr.rs
@@ -1,14 +1,15 @@
 //! Code generation for `#[graphql_union]` macro.
 
-use std::{mem, ops::Deref as _};
+use std::mem;
 
 use proc_macro2::{Span, TokenStream};
 use quote::{quote, ToTokens as _};
 use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
 
 use crate::{
+    common::parse,
     result::GraphQLScope,
-    util::{path_eq_single, span_container::SpanContainer, unparenthesize},
+    util::{path_eq_single, span_container::SpanContainer},
 };
 
 use super::{
@@ -27,17 +28,8 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
             "#[graphql_union] attribute is applicable to trait definitions only",
         )
     })?;
-
-    let mut trait_attrs = Vec::with_capacity(ast.attrs.len() + 1);
-    trait_attrs.push(parse_quote! { #[graphql_union(#attr_args)] });
-    trait_attrs.extend_from_slice(&ast.attrs);
-
-    // Remove repeated attributes from the definition, to omit duplicate expansion.
-    ast.attrs = ast
-        .attrs
-        .into_iter()
-        .filter(|attr| !path_eq_single(&attr.path, "graphql_union"))
-        .collect();
+    let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs);
+    ast.attrs = parse::attr::strip("graphql_union", ast.attrs);
 
     let meta = UnionMeta::from_attrs("graphql_union", &trait_attrs)?;
 
@@ -98,7 +90,6 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
         scalar: meta.scalar.map(SpanContainer::into_inner),
         generics: ast.generics.clone(),
         variants,
-        span: trait_span,
     };
 
     Ok(quote! {
@@ -149,7 +140,7 @@ fn parse_variant_from_trait_method(
     let method_span = method.sig.span();
     let method_ident = &method.sig.ident;
 
-    let ty = parse_trait_method_output_type(&method.sig)
+    let ty = parse::downcaster::output_type(&method.sig.output)
         .map_err(|span| {
             ERR.emit_custom(
                 span,
@@ -157,7 +148,7 @@ fn parse_variant_from_trait_method(
             )
         })
         .ok()?;
-    let method_context_ty = parse_trait_method_input_args(&method.sig)
+    let method_context_ty = parse::downcaster::context_ty(&method.sig)
         .map_err(|span| {
             ERR.emit_custom(
                 span,
@@ -168,7 +159,7 @@ fn parse_variant_from_trait_method(
     if let Some(is_async) = &method.sig.asyncness {
         ERR.emit_custom(
             is_async.span(),
-            "doesn't support async union variants resolvers yet",
+            "async downcast to union variants is not supported",
         );
         return None;
     }
@@ -220,85 +211,3 @@ fn parse_variant_from_trait_method(
         span: method_span,
     })
 }
-
-/// Parses type of [GraphQL union][1] variant from the return type of trait method.
-///
-/// If return type is invalid, then returns the [`Span`] to display the corresponding error at.
-///
-/// [1]: https://spec.graphql.org/June2018/#sec-Unions
-fn parse_trait_method_output_type(sig: &syn::Signature) -> Result<syn::Type, Span> {
-    let ret_ty = match &sig.output {
-        syn::ReturnType::Type(_, ty) => ty.deref(),
-        _ => return Err(sig.span()),
-    };
-
-    let path = match unparenthesize(ret_ty) {
-        syn::Type::Path(syn::TypePath { qself: None, path }) => path,
-        _ => return Err(ret_ty.span()),
-    };
-
-    let (ident, args) = match path.segments.last() {
-        Some(syn::PathSegment {
-            ident,
-            arguments: syn::PathArguments::AngleBracketed(generic),
-        }) => (ident, &generic.args),
-        _ => return Err(ret_ty.span()),
-    };
-
-    if ident.unraw() != "Option" {
-        return Err(ret_ty.span());
-    }
-
-    if args.len() != 1 {
-        return Err(ret_ty.span());
-    }
-    let var_ty = match args.first() {
-        Some(syn::GenericArgument::Type(inner_ty)) => match unparenthesize(inner_ty) {
-            syn::Type::Reference(inner_ty) => {
-                if inner_ty.mutability.is_some() {
-                    return Err(inner_ty.span());
-                }
-                unparenthesize(inner_ty.elem.deref()).clone()
-            }
-            _ => return Err(ret_ty.span()),
-        },
-        _ => return Err(ret_ty.span()),
-    };
-    Ok(var_ty)
-}
-
-/// Parses trait method input arguments and validates them to be acceptable for resolving into
-/// [GraphQL union][1] variant type. Returns type of the context used in input arguments, if any.
-///
-/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at.
-///
-/// [1]: https://spec.graphql.org/June2018/#sec-Unions
-fn parse_trait_method_input_args(sig: &syn::Signature) -> Result<Option<syn::Type>, Span> {
-    match sig.receiver() {
-        Some(syn::FnArg::Receiver(rcv)) => {
-            if rcv.reference.is_none() || rcv.mutability.is_some() {
-                return Err(rcv.span());
-            }
-        }
-        _ => return Err(sig.span()),
-    }
-
-    if sig.inputs.len() > 2 {
-        return Err(sig.inputs.span());
-    }
-
-    let second_arg_ty = match sig.inputs.iter().nth(1) {
-        Some(syn::FnArg::Typed(arg)) => arg.ty.deref(),
-        None => return Ok(None),
-        _ => return Err(sig.inputs.span()),
-    };
-    match unparenthesize(second_arg_ty) {
-        syn::Type::Reference(ref_ty) => {
-            if ref_ty.mutability.is_some() {
-                return Err(ref_ty.span());
-            }
-            Ok(Some(ref_ty.elem.deref().clone()))
-        }
-        ty => Err(ty.span()),
-    }
-}
diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs
index 785fe0e83..9d5ac7ffd 100644
--- a/juniper_codegen/src/graphql_union/derive.rs
+++ b/juniper_codegen/src/graphql_union/derive.rs
@@ -6,8 +6,7 @@ use quote::{quote, ToTokens};
 use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields};
 
 use crate::{
-    result::GraphQLScope,
-    util::{span_container::SpanContainer, unparenthesize},
+    common::parse::TypeExt as _, result::GraphQLScope, util::span_container::SpanContainer,
 };
 
 use super::{
@@ -85,7 +84,6 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
         scalar: meta.scalar.map(SpanContainer::into_inner),
         generics: ast.generics,
         variants,
-        span: enum_span,
     })
 }
 
@@ -115,7 +113,7 @@ fn parse_variant_from_enum_variant(
             let mut iter = fields.unnamed.iter();
             let first = iter.next().unwrap();
             if iter.next().is_none() {
-                Ok(unparenthesize(&first.ty).clone())
+                Ok(first.ty.unparenthesized().clone())
             } else {
                 Err(fields.span())
             }
@@ -214,6 +212,5 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
         scalar: meta.scalar.map(SpanContainer::into_inner),
         generics: ast.generics,
         variants,
-        span: struct_span,
     })
 }
diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs
index 6ed0a3b55..28586e37e 100644
--- a/juniper_codegen/src/graphql_union/mod.rs
+++ b/juniper_codegen/src/graphql_union/mod.rs
@@ -13,60 +13,16 @@ use syn::{
     parse::{Parse, ParseStream},
     parse_quote,
     spanned::Spanned as _,
+    token,
 };
 
-use crate::util::{filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _};
-
-/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of
-/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related
-/// to the `$another` struct (a later one).
-///
-/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
-/// By default, [`SpanContainer::span_ident`] is used.
-macro_rules! try_merge_opt {
-    ($field:ident: $self:ident, $another:ident => $span:ident) => {{
-        if let Some(v) = $self.$field {
-            $another
-                .$field
-                .replace(v)
-                .none_or_else(|dup| dup_attr_err(dup.$span()))?;
-        }
-        $another.$field
-    }};
-
-    ($field:ident: $self:ident, $another:ident) => {
-        try_merge_opt!($field: $self, $another => span_ident)
-    };
-}
-
-/// Attempts to merge a [`HashMap`]ed `$field` of a `$self` struct with the same `$field` of
-/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error
-/// with a [`Span`] related to the `$another` struct (a later one).
-///
-/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
-/// By default, [`SpanContainer::span_ident`] is used.
-macro_rules! try_merge_hashmap {
-    ($field:ident: $self:ident, $another:ident => $span:ident) => {{
-        if !$self.$field.is_empty() {
-            for (ty, rslvr) in $self.$field {
-                $another
-                    .$field
-                    .insert(ty, rslvr)
-                    .none_or_else(|dup| dup_attr_err(dup.$span()))?;
-            }
-        }
-        $another.$field
-    }};
-
-    ($field:ident: $self:ident, $another:ident) => {
-        try_merge_hashmap!($field: $self, $another => span_ident)
-    };
-}
-
-/// Creates and returns duplication error pointing to the given `span`.
-fn dup_attr_err(span: Span) -> syn::Error {
-    syn::Error::new(span, "duplicated attribute")
-}
+use crate::{
+    common::parse::{
+        attr::{err, OptionExt as _},
+        ParseBufferExt as _,
+    },
+    util::{filter_attrs, get_doc_comment, span_container::SpanContainer},
+};
 
 /// Helper alias for the type of [`UnionMeta::external_resolvers`] field.
 type UnionMetaResolvers = HashMap<syn::Type, SpanContainer<syn::ExprPath>>;
@@ -131,10 +87,10 @@ impl Parse for UnionMeta {
         let mut output = Self::default();
 
         while !input.is_empty() {
-            let ident: syn::Ident = input.parse()?;
+            let ident = input.parse::<syn::Ident>()?;
             match ident.to_string().as_str() {
                 "name" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let name = input.parse::<syn::LitStr>()?;
                     output
                         .name
@@ -143,10 +99,10 @@ impl Parse for UnionMeta {
                             Some(name.span()),
                             name.value(),
                         ))
-                        .none_or_else(|_| dup_attr_err(ident.span()))?
+                        .none_or_else(|_| err::dup_arg(&ident))?
                 }
                 "desc" | "description" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let desc = input.parse::<syn::LitStr>()?;
                     output
                         .description
@@ -155,45 +111,43 @@ impl Parse for UnionMeta {
                             Some(desc.span()),
                             desc.value(),
                         ))
-                        .none_or_else(|_| dup_attr_err(ident.span()))?
+                        .none_or_else(|_| err::dup_arg(&ident))?
                 }
                 "ctx" | "context" | "Context" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let ctx = input.parse::<syn::Type>()?;
                     output
                         .context
                         .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx))
-                        .none_or_else(|_| dup_attr_err(ident.span()))?
+                        .none_or_else(|_| err::dup_arg(&ident))?
                 }
                 "scalar" | "Scalar" | "ScalarValue" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let scl = input.parse::<syn::Type>()?;
                     output
                         .scalar
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
-                        .none_or_else(|_| dup_attr_err(ident.span()))?
+                        .none_or_else(|_| err::dup_arg(&ident))?
                 }
                 "on" => {
                     let ty = input.parse::<syn::Type>()?;
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let rslvr = input.parse::<syn::ExprPath>()?;
                     let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr);
                     let rslvr_span = rslvr_spanned.span_joined();
                     output
                         .external_resolvers
                         .insert(ty, rslvr_spanned)
-                        .none_or_else(|_| dup_attr_err(rslvr_span))?
+                        .none_or_else(|_| err::dup_arg(rslvr_span))?
                 }
                 "internal" => {
                     output.is_internal = true;
                 }
-                _ => {
-                    return Err(syn::Error::new(ident.span(), "unknown attribute"));
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
                 }
             }
-            if input.lookahead1().peek(syn::Token![,]) {
-                input.parse::<syn::Token![,]>()?;
-            }
+            input.try_parse::<token::Comma>()?;
         }
 
         Ok(output)
@@ -201,19 +155,22 @@ impl Parse for UnionMeta {
 }
 
 impl UnionMeta {
-    /// Tries to merge two [`UnionMeta`]s into single one, reporting about duplicates, if any.
+    /// Tries to merge two [`UnionMeta`]s into a single one, reporting about duplicates, if any.
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         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),
-            external_resolvers: try_merge_hashmap!(external_resolvers: self, another => span_joined),
+            external_resolvers: try_merge_hashmap!(
+                external_resolvers: self, another => span_joined
+            ),
             is_internal: self.is_internal || another.is_internal,
         })
     }
 
-    /// Parses [`UnionMeta`] from the given multiple `name`d attributes placed on type definition.
+    /// Parses [`UnionMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a type
+    /// definition.
     pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
         let mut meta = filter_attrs(name, attrs)
             .map(|attr| attr.parse_args())
@@ -254,27 +211,25 @@ impl Parse for UnionVariantMeta {
         let mut output = Self::default();
 
         while !input.is_empty() {
-            let ident: syn::Ident = input.parse()?;
+            let ident = input.parse::<syn::Ident>()?;
             match ident.to_string().as_str() {
                 "ignore" | "skip" => output
                     .ignore
                     .replace(SpanContainer::new(ident.span(), None, ident.clone()))
-                    .none_or_else(|_| dup_attr_err(ident.span()))?,
+                    .none_or_else(|_| err::dup_arg(&ident))?,
                 "with" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let rslvr = input.parse::<syn::ExprPath>()?;
                     output
                         .external_resolver
                         .replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr))
-                        .none_or_else(|_| dup_attr_err(ident.span()))?
+                        .none_or_else(|_| err::dup_arg(&ident))?
                 }
-                _ => {
-                    return Err(syn::Error::new(ident.span(), "unknown attribute"));
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
                 }
             }
-            if input.lookahead1().peek(syn::Token![,]) {
-                input.parse::<syn::Token![,]>()?;
-            }
+            input.try_parse::<token::Comma>()?;
         }
 
         Ok(output)
@@ -282,7 +237,7 @@ impl Parse for UnionVariantMeta {
 }
 
 impl UnionVariantMeta {
-    /// Tries to merge two [`UnionVariantMeta`]s into single one, reporting about duplicates, if
+    /// Tries to merge two [`UnionVariantMeta`]s into a single one, reporting about duplicates, if
     /// any.
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         Ok(Self {
@@ -291,7 +246,7 @@ impl UnionVariantMeta {
         })
     }
 
-    /// Parses [`UnionVariantMeta`] from the given multiple `name`d attributes placed on
+    /// Parses [`UnionVariantMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a
     /// variant/field/method definition.
     pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
         filter_attrs(name, attrs)
@@ -356,6 +311,8 @@ struct UnionDefinition {
     pub ty: syn::Type,
 
     /// Generics of the Rust type that this [GraphQL union][1] is implemented for.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
     pub generics: syn::Generics,
 
     /// Indicator whether code should be generated for a trait object, rather than for a regular
@@ -390,11 +347,6 @@ struct UnionDefinition {
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
     pub variants: Vec<UnionVariantDefinition>,
-
-    /// [`Span`] that points to the Rust source code which defines this [GraphQL union][1].
-    ///
-    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub span: Span,
 }
 
 impl ToTokens for UnionDefinition {
@@ -432,7 +384,7 @@ impl ToTokens for UnionDefinition {
             let var_check = &var.resolver_check;
             quote! {
                 if #var_check {
-                    return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(&())
+                    return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)
                         .unwrap().to_string();
                 }
             }
@@ -442,15 +394,15 @@ impl ToTokens for UnionDefinition {
         let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| {
             let var_ty = &var.ty;
 
-            let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(&())) };
+            let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)) };
             quote! {
                 if type_name == #get_name.unwrap() {
                     return ::juniper::IntoResolvable::into(
                         { #expr },
-                        executor.context()
+                        executor.context(),
                     )
                     .and_then(|res| match res {
-                        Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
+                        Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r),
                         None => Ok(::juniper::Value::null()),
                     });
                 }
@@ -464,19 +416,19 @@ impl ToTokens for UnionDefinition {
                     let var_ty = &var.ty;
 
                     let get_name = quote! {
-                        (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(&()))
+                        (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info))
                     };
                     quote! {
                         if type_name == #get_name.unwrap() {
                             let res = ::juniper::IntoResolvable::into(
                                 { #expr },
-                                executor.context()
+                                executor.context(),
                             );
                             return Box::pin(async move {
                                 match res? {
                                     Some((ctx, r)) => {
                                         let subexec = executor.replaced_context(ctx);
-                                        subexec.resolve_with_ctx_async(&(), &r).await
+                                        subexec.resolve_with_ctx_async(info, &r).await
                                     },
                                     None => Ok(::juniper::Value::null()),
                                 }
@@ -494,8 +446,7 @@ impl ToTokens for UnionDefinition {
         if self.scalar.is_none() {
             ext_generics.params.push(parse_quote! { #scalar });
             ext_generics
-                .where_clause
-                .get_or_insert_with(|| parse_quote! { where })
+                .make_where_clause()
                 .predicates
                 .push(parse_quote! { #scalar: ::juniper::ScalarValue });
         }
@@ -531,10 +482,10 @@ impl ToTokens for UnionDefinition {
                 ) -> ::juniper::meta::MetaType<'r, #scalar>
                 where #scalar: 'r,
                 {
-                    let types = &[
-                        #( registry.get_type::<&#var_types>(&(())), )*
+                    let types = [
+                        #( registry.get_type::<#var_types>(info), )*
                     ];
-                    registry.build_union_type::<#ty_full>(info, types)
+                    registry.build_union_type::<#ty_full>(info, &types)
                     #description
                     .into_meta()
                 }
@@ -556,7 +507,7 @@ impl ToTokens for UnionDefinition {
                 fn concrete_type_name(
                     &self,
                     context: &Self::Context,
-                    _: &Self::TypeInfo,
+                    info: &Self::TypeInfo,
                 ) -> String {
                     #( #match_names )*
                     panic!(
@@ -568,7 +519,7 @@ impl ToTokens for UnionDefinition {
 
                 fn resolve_into_type(
                     &self,
-                    _: &Self::TypeInfo,
+                    info: &Self::TypeInfo,
                     type_name: &str,
                     _: Option<&[::juniper::Selection<#scalar>]>,
                     executor: &::juniper::Executor<Self::Context, #scalar>,
@@ -590,7 +541,7 @@ impl ToTokens for UnionDefinition {
             {
                 fn resolve_into_type_async<'b>(
                     &'b self,
-                    _: &'b Self::TypeInfo,
+                    info: &'b Self::TypeInfo,
                     type_name: &str,
                     _: Option<&'b [::juniper::Selection<'b, #scalar>]>,
                     executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar>
diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs
index fd8247000..66a5879ee 100644
--- a/juniper_codegen/src/impl_object.rs
+++ b/juniper_codegen/src/impl_object.rs
@@ -201,18 +201,12 @@ fn create(
         description: _impl.description,
         fields,
         generics: _impl.generics.clone(),
-        interfaces: if !_impl.attrs.interfaces.is_empty() {
-            Some(
-                _impl
-                    .attrs
-                    .interfaces
-                    .into_iter()
-                    .map(SpanContainer::into_inner)
-                    .collect(),
-            )
-        } else {
-            None
-        },
+        interfaces: _impl
+            .attrs
+            .interfaces
+            .into_iter()
+            .map(SpanContainer::into_inner)
+            .collect(),
         include_type_generics: false,
         generic_scalar: false,
         no_async: _impl.attrs.no_async.is_some(),
diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs
index 560dea931..74dc6d7e3 100644
--- a/juniper_codegen/src/impl_scalar.rs
+++ b/juniper_codegen/src/impl_scalar.rs
@@ -26,12 +26,10 @@ fn get_first_method_arg(
     inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
 ) -> Option<syn::Ident> {
     if let Some(fn_arg) = inputs.first() {
-        match fn_arg {
-            syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
-                syn::Pat::Ident(pat_ident) => return Some(pat_ident.ident.clone()),
-                _ => (),
-            },
-            _ => (),
+        if let syn::FnArg::Typed(pat_type) = fn_arg {
+            if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
+                return Some(pat_ident.ident.clone());
+            }
         }
     }
 
@@ -70,19 +68,11 @@ fn get_enum_type(return_type: &Option<syn::Type>) -> Option<syn::PathSegment> {
                                     }
                                 });
 
-                            if let Some(generic_type_arg) = generic_type_arg {
-                                match generic_type_arg {
-                                    syn::GenericArgument::Type(the_type) => match the_type {
-                                        syn::Type::Path(type_path) => {
-                                            if let Some(path_segment) =
-                                                type_path.path.segments.first()
-                                            {
-                                                return Some(path_segment.clone());
-                                            }
-                                        }
-                                        _ => (),
-                                    },
-                                    _ => (),
+                            if let Some(syn::GenericArgument::Type(syn::Type::Path(type_path))) =
+                                generic_type_arg
+                            {
+                                if let Some(path_segment) = type_path.path.segments.first() {
+                                    return Some(path_segment.clone());
                                 }
                             }
                         }
@@ -115,13 +105,10 @@ impl syn::parse::Parse for ScalarCodegenInput {
         let custom_data_type_is_struct: bool =
             !parse_custom_scalar_value_impl.generics.params.is_empty();
 
-        match *parse_custom_scalar_value_impl.self_ty {
-            syn::Type::Path(type_path) => {
-                if let Some(path_segment) = type_path.path.segments.first() {
-                    impl_for_type = Some(path_segment.clone());
-                }
+        if let syn::Type::Path(type_path) = *parse_custom_scalar_value_impl.self_ty {
+            if let Some(path_segment) = type_path.path.segments.first() {
+                impl_for_type = Some(path_segment.clone());
             }
-            _ => (),
         }
 
         for impl_item in parse_custom_scalar_value_impl.items {
diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs
index d55018d23..a0d498b2d 100644
--- a/juniper_codegen/src/lib.rs
+++ b/juniper_codegen/src/lib.rs
@@ -12,6 +12,102 @@ extern crate proc_macro;
 mod result;
 mod util;
 
+// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
+//         doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
+//         and so we cannot move the definition into a sub-module and use the `#[macro_export]`
+//         attribute.
+/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of
+/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related
+/// to the `$another` struct (a later one).
+///
+/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
+/// By default, [`SpanContainer::span_ident`] is used.
+///
+/// [`Span`]: proc_macro2::Span
+/// [`SpanContainer`]: crate::util::span_container::SpanContainer
+/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
+macro_rules! try_merge_opt {
+    ($field:ident: $self:ident, $another:ident => $span:ident) => {{
+        if let Some(v) = $self.$field {
+            $another
+                .$field
+                .replace(v)
+                .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?;
+        }
+        $another.$field
+    }};
+
+    ($field:ident: $self:ident, $another:ident) => {
+        try_merge_opt!($field: $self, $another => span_ident)
+    };
+}
+
+// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
+//         doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
+//         and so we cannot move the definition into a sub-module and use the `#[macro_export]`
+//         attribute.
+/// Attempts to merge a [`HashMap`] `$field` of a `$self` struct with the same `$field` of
+/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error
+/// with a [`Span`] related to the `$another` struct (a later one).
+///
+/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
+/// By default, [`SpanContainer::span_ident`] is used.
+///
+/// [`HashMap`]: std::collections::HashMap
+/// [`Span`]: proc_macro2::Span
+/// [`SpanContainer`]: crate::util::span_container::SpanContainer
+/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
+macro_rules! try_merge_hashmap {
+    ($field:ident: $self:ident, $another:ident => $span:ident) => {{
+        if !$self.$field.is_empty() {
+            for (ty, rslvr) in $self.$field {
+                $another
+                    .$field
+                    .insert(ty, rslvr)
+                    .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?;
+            }
+        }
+        $another.$field
+    }};
+
+    ($field:ident: $self:ident, $another:ident) => {
+        try_merge_hashmap!($field: $self, $another => span_ident)
+    };
+}
+
+// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
+//         doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
+//         and so we cannot move the definition into a sub-module and use the `#[macro_export]`
+//         attribute.
+/// Attempts to merge a [`HashSet`] `$field` of a `$self` struct with the same `$field` of
+/// `$another` struct. If some [`HashSet`] entries are duplicated, then throws a duplication error
+/// with a [`Span`] related to the `$another` struct (a later one).
+///
+/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
+/// By default, [`SpanContainer::span_ident`] is used.
+///
+/// [`HashSet`]: std::collections::HashSet
+/// [`Span`]: proc_macro2::Span
+/// [`SpanContainer`]: crate::util::span_container::SpanContainer
+/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
+macro_rules! try_merge_hashset {
+    ($field:ident: $self:ident, $another:ident => $span:ident) => {{
+        if !$self.$field.is_empty() {
+            for ty in $self.$field {
+                $another
+                    .$field
+                    .replace(ty)
+                    .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?;
+            }
+        }
+        $another.$field
+    }};
+
+    ($field:ident: $self:ident, $another:ident) => {
+        try_merge_hashset!($field: $self, $another => span_ident)
+    };
+}
+
 mod derive_enum;
 mod derive_input_object;
 mod derive_object;
@@ -19,6 +115,8 @@ mod derive_scalar_value;
 mod impl_object;
 mod impl_scalar;
 
+mod common;
+mod graphql_interface;
 mod graphql_union;
 
 use proc_macro::TokenStream;
@@ -452,6 +550,14 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
     ))
 }
 
+#[proc_macro_error]
+#[proc_macro_attribute]
+pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
+    self::graphql_interface::attr::expand(attr.into(), body.into())
+        .unwrap_or_abort()
+        .into()
+}
+
 /// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and
 /// structs.
 ///
diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs
index 767051432..68c878ffa 100644
--- a/juniper_codegen/src/result.rs
+++ b/juniper_codegen/src/result.rs
@@ -10,10 +10,11 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/";
 
 #[allow(unused_variables)]
 pub enum GraphQLScope {
+    InterfaceAttr,
     UnionAttr,
+    UnionDerive,
     DeriveObject,
     DeriveInputObject,
-    UnionDerive,
     DeriveEnum,
     DeriveScalar,
     ImplScalar,
@@ -23,9 +24,10 @@ pub enum GraphQLScope {
 impl GraphQLScope {
     pub fn spec_section(&self) -> &str {
         match self {
+            Self::InterfaceAttr => "#sec-Interfaces",
+            Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
             Self::DeriveObject | Self::ImplObject => "#sec-Objects",
             Self::DeriveInputObject => "#sec-Input-Objects",
-            Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
             Self::DeriveEnum => "#sec-Enums",
             Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
         }
@@ -35,9 +37,10 @@ impl GraphQLScope {
 impl fmt::Display for GraphQLScope {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let name = match self {
+            Self::InterfaceAttr => "interface",
+            Self::UnionAttr | Self::UnionDerive => "union",
             Self::DeriveObject | Self::ImplObject => "object",
             Self::DeriveInputObject => "input object",
-            Self::UnionAttr | Self::UnionDerive => "union",
             Self::DeriveEnum => "enum",
             Self::DeriveScalar | Self::ImplScalar => "scalar",
         };
diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs
index ac6e5a5ef..e597eb26b 100644
--- a/juniper_codegen/src/util/mod.rs
+++ b/juniper_codegen/src/util/mod.rs
@@ -1,23 +1,24 @@
 #![allow(clippy::single_match)]
 
 pub mod duplicate;
-pub mod option_ext;
 pub mod parse_impl;
 pub mod span_container;
 
-use std::ops::Deref as _;
+use std::collections::HashMap;
 
 use proc_macro2::{Span, TokenStream};
 use proc_macro_error::abort;
 use quote::quote;
 use span_container::SpanContainer;
-use std::collections::HashMap;
 use syn::{
-    parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Lit, Meta, MetaList,
-    MetaNameValue, NestedMeta, Token,
+    parse::{Parse, ParseStream},
+    parse_quote,
+    punctuated::Punctuated,
+    spanned::Spanned,
+    token, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
 };
 
-pub use self::option_ext::OptionExt;
+use crate::common::parse::ParseBufferExt as _;
 
 /// Returns the name of a type.
 /// If the type does not end in a simple ident, `None` is returned.
@@ -74,15 +75,6 @@ pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool {
     }
 }
 
-/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested
-/// [`syn::TypeParen`]s asap).
-pub fn unparenthesize(ty: &syn::Type) -> &syn::Type {
-    match ty {
-        syn::Type::Paren(ty) => unparenthesize(ty.elem.deref()),
-        _ => ty,
-    }
-}
-
 #[derive(Debug)]
 pub struct DeprecationAttr {
     pub reason: Option<String>,
@@ -232,11 +224,18 @@ fn get_doc_attr(attrs: &[Attribute]) -> Option<Vec<MetaNameValue>> {
 pub fn to_camel_case(s: &str) -> String {
     let mut dest = String::new();
 
-    // handle '_' to be more friendly with the
-    // _var convention for unused variables
-    let s_iter = if s.starts_with('_') { &s[1..] } else { s }
-        .split('_')
-        .enumerate();
+    // Handle `_` and `__` to be more friendly with the `_var` convention for unused variables, and
+    // GraphQL introspection identifiers.
+    let s_iter = if s.starts_with("__") {
+        dest.push_str("__");
+        &s[2..]
+    } else if s.starts_with('_') {
+        &s[1..]
+    } else {
+        s
+    }
+    .split('_')
+    .enumerate();
 
     for (i, part) in s_iter {
         if i > 0 && part.len() == 1 {
@@ -307,15 +306,15 @@ pub struct ObjectAttributes {
     pub is_internal: bool,
 }
 
-impl syn::parse::Parse for ObjectAttributes {
-    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
+impl Parse for ObjectAttributes {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
         let mut output = Self::default();
 
         while !input.is_empty() {
-            let ident: syn::Ident = input.parse()?;
+            let ident = input.parse_any_ident()?;
             match ident.to_string().as_str() {
                 "name" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::LitStr>()?;
                     output.name = Some(SpanContainer::new(
                         ident.span(),
@@ -324,7 +323,7 @@ impl syn::parse::Parse for ObjectAttributes {
                     ));
                 }
                 "description" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::LitStr>()?;
                     output.description = Some(SpanContainer::new(
                         ident.span(),
@@ -333,7 +332,7 @@ impl syn::parse::Parse for ObjectAttributes {
                     ));
                 }
                 "context" | "Context" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     // TODO: remove legacy support for string based Context.
                     let ctx = if let Ok(val) = input.parse::<syn::LitStr>() {
                         eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated");
@@ -345,19 +344,15 @@ impl syn::parse::Parse for ObjectAttributes {
                     output.context = Some(SpanContainer::new(ident.span(), Some(ctx.span()), ctx));
                 }
                 "scalar" | "Scalar" => {
-                    input.parse::<syn::Token![=]>()?;
+                    input.parse::<token::Eq>()?;
                     let val = input.parse::<syn::Type>()?;
                     output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val));
                 }
-                "interfaces" => {
-                    input.parse::<syn::Token![=]>()?;
-                    let content;
-                    syn::bracketed!(content in input);
-                    output.interfaces =
-                        syn::punctuated::Punctuated::<syn::Type, syn::Token![,]>::parse_terminated(
-                            &content,
-                        )?
-                        .into_iter()
+                "impl" | "implements" | "interfaces" => {
+                    input.parse::<token::Eq>()?;
+                    output.interfaces = input.parse_maybe_wrapped_and_punctuated::<
+                        syn::Type, token::Bracket, token::Comma,
+                    >()?.into_iter()
                         .map(|interface| {
                             SpanContainer::new(ident.span(), Some(interface.span()), interface)
                         })
@@ -374,9 +369,7 @@ impl syn::parse::Parse for ObjectAttributes {
                     return Err(syn::Error::new(ident.span(), "unknown attribute"));
                 }
             }
-            if input.lookahead1().peek(syn::Token![,]) {
-                input.parse::<syn::Token![,]>()?;
-            }
+            input.try_parse::<token::Comma>()?;
         }
 
         Ok(output)
@@ -384,7 +377,7 @@ impl syn::parse::Parse for ObjectAttributes {
 }
 
 impl ObjectAttributes {
-    pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result<Self> {
+    pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::Result<Self> {
         let attr_opt = find_graphql_attr(attrs);
         if let Some(attr) = attr_opt {
             // Need to unwrap  outer (), which are not present for proc macro attributes,
@@ -411,8 +404,8 @@ pub struct FieldAttributeArgument {
     pub description: Option<syn::LitStr>,
 }
 
-impl parse::Parse for FieldAttributeArgument {
-    fn parse(input: parse::ParseStream) -> parse::Result<Self> {
+impl Parse for FieldAttributeArgument {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
         let name = input.parse()?;
 
         let mut arg = Self {
@@ -426,7 +419,7 @@ impl parse::Parse for FieldAttributeArgument {
         syn::parenthesized!(content in input);
         while !content.is_empty() {
             let name = content.parse::<syn::Ident>()?;
-            content.parse::<Token![=]>()?;
+            content.parse::<token::Eq>()?;
 
             match name.to_string().as_str() {
                 "name" => {
@@ -443,7 +436,7 @@ impl parse::Parse for FieldAttributeArgument {
             }
 
             // Discard trailing comma.
-            content.parse::<Token![,]>().ok();
+            content.parse::<token::Comma>().ok();
         }
 
         Ok(arg)
@@ -465,13 +458,13 @@ enum FieldAttribute {
     Default(SpanContainer<Option<syn::Expr>>),
 }
 
-impl parse::Parse for FieldAttribute {
-    fn parse(input: parse::ParseStream) -> parse::Result<Self> {
+impl Parse for FieldAttribute {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
         let ident = input.parse::<syn::Ident>()?;
 
         match ident.to_string().as_str() {
             "name" => {
-                input.parse::<Token![=]>()?;
+                input.parse::<token::Eq>()?;
                 let lit = input.parse::<syn::LitStr>()?;
                 let raw = lit.value();
                 if !is_valid_name(&raw) {
@@ -485,7 +478,7 @@ impl parse::Parse for FieldAttribute {
                 }
             }
             "description" => {
-                input.parse::<Token![=]>()?;
+                input.parse::<token::Eq>()?;
                 let lit = input.parse::<syn::LitStr>()?;
                 Ok(FieldAttribute::Description(SpanContainer::new(
                     ident.span(),
@@ -494,8 +487,8 @@ impl parse::Parse for FieldAttribute {
                 )))
             }
             "deprecated" | "deprecation" => {
-                let reason = if input.peek(Token![=]) {
-                    input.parse::<Token![=]>()?;
+                let reason = if input.peek(token::Eq) {
+                    input.parse::<token::Eq>()?;
                     Some(input.parse::<syn::LitStr>()?)
                 } else {
                     None
@@ -516,7 +509,7 @@ impl parse::Parse for FieldAttribute {
             "arguments" => {
                 let arg_content;
                 syn::parenthesized!(arg_content in input);
-                let args = Punctuated::<FieldAttributeArgument, Token![,]>::parse_terminated(
+                let args = Punctuated::<FieldAttributeArgument, token::Comma>::parse_terminated(
                     &arg_content,
                 )?;
                 let map = args
@@ -526,8 +519,8 @@ impl parse::Parse for FieldAttribute {
                 Ok(FieldAttribute::Arguments(map))
             }
             "default" => {
-                let default_expr = if input.peek(Token![=]) {
-                    input.parse::<Token![=]>()?;
+                let default_expr = if input.peek(token::Eq) {
+                    input.parse::<token::Eq>()?;
                     let lit = input.parse::<syn::LitStr>()?;
                     let default_expr = lit.parse::<syn::Expr>()?;
                     SpanContainer::new(ident.span(), Some(lit.span()), Some(default_expr))
@@ -555,9 +548,9 @@ pub struct FieldAttributes {
     pub default: Option<SpanContainer<Option<syn::Expr>>>,
 }
 
-impl parse::Parse for FieldAttributes {
-    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
-        let items = Punctuated::<FieldAttribute, Token![,]>::parse_terminated(&input)?;
+impl Parse for FieldAttributes {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let items = Punctuated::<FieldAttribute, token::Comma>::parse_terminated(&input)?;
 
         let mut output = Self::default();
 
@@ -596,7 +589,7 @@ impl FieldAttributes {
     pub fn from_attrs(
         attrs: &[syn::Attribute],
         _mode: FieldAttributeParseMode,
-    ) -> syn::parse::Result<Self> {
+    ) -> syn::Result<Self> {
         let doc_comment = get_doc_comment(&attrs);
         let deprecation = get_deprecated(&attrs);
 
@@ -669,7 +662,7 @@ pub struct GraphQLTypeDefiniton {
     pub description: Option<String>,
     pub fields: Vec<GraphQLTypeDefinitionField>,
     pub generics: syn::Generics,
-    pub interfaces: Option<Vec<syn::Type>>,
+    pub interfaces: Vec<syn::Type>,
     // Due to syn parsing differences,
     // when parsing an impl the type generics are included in the type
     // directly, but in syn::DeriveInput, the type generics are
@@ -813,13 +806,17 @@ impl GraphQLTypeDefiniton {
             .as_ref()
             .map(|description| quote!( .description(#description) ));
 
-        let interfaces = self.interfaces.as_ref().map(|items| {
-            quote!(
+        let interfaces = if !self.interfaces.is_empty() {
+            let interfaces_ty = &self.interfaces;
+
+            Some(quote!(
                 .interfaces(&[
-                    #( registry.get_type::< #items >(&()) ,)*
+                    #( registry.get_type::<#interfaces_ty>(&()) ,)*
                 ])
-            )
-        });
+            ))
+        } else {
+            None
+        };
 
         // Preserve the original type_generics before modification,
         // since alteration makes them invalid if self.generic_scalar
@@ -928,7 +925,29 @@ impl GraphQLTypeDefiniton {
                 .push(parse_quote!( #scalar: Send + Sync ));
             where_async.predicates.push(parse_quote!(Self: Sync));
 
-            // FIXME: add where clause for interfaces.
+            let as_dyn_value = if !self.interfaces.is_empty() {
+                Some(quote! {
+                    #[automatically_derived]
+                    impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens
+                    #where_async
+                    {
+                        type Context = <Self as ::juniper::GraphQLValue<#scalar>>::Context;
+                        type TypeInfo = <Self as ::juniper::GraphQLValue<#scalar>>::TypeInfo;
+
+                        #[inline]
+                        fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> {
+                            self
+                        }
+
+                        #[inline]
+                        fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<#scalar, Self::Context, Self::TypeInfo> {
+                            self
+                        }
+                    }
+                })
+            } else {
+                None
+            };
 
             quote!(
                 impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #type_generics_tokens
@@ -956,6 +975,8 @@ impl GraphQLTypeDefiniton {
                         }
                     }
                 }
+
+                #as_dyn_value
             )
         };
 
@@ -1144,13 +1165,17 @@ impl GraphQLTypeDefiniton {
             .as_ref()
             .map(|description| quote!( .description(#description) ));
 
-        let interfaces = self.interfaces.as_ref().map(|items| {
-            quote!(
+        let interfaces = if !self.interfaces.is_empty() {
+            let interfaces_ty = &self.interfaces;
+
+            Some(quote!(
                 .interfaces(&[
-                    #( registry.get_type::< #items >(&()) ,)*
+                    #( registry.get_type::<#interfaces_ty>(&()) ,)*
                 ])
-            )
-        });
+            ))
+        } else {
+            None
+        };
 
         // Preserve the original type_generics before modification,
         // since alteration makes them invalid if self.generic_scalar
@@ -1917,6 +1942,7 @@ mod test {
     fn test_to_camel_case() {
         assert_eq!(&to_camel_case("test")[..], "test");
         assert_eq!(&to_camel_case("_test")[..], "test");
+        assert_eq!(&to_camel_case("__test")[..], "__test");
         assert_eq!(&to_camel_case("first_second")[..], "firstSecond");
         assert_eq!(&to_camel_case("first_")[..], "first");
         assert_eq!(&to_camel_case("a_b_c")[..], "aBC");
diff --git a/juniper_codegen/src/util/option_ext.rs b/juniper_codegen/src/util/option_ext.rs
deleted file mode 100644
index 3007abd65..000000000
--- a/juniper_codegen/src/util/option_ext.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-/// Handy extension of [`Option`] methods used in this crate.
-pub trait OptionExt {
-    type Inner;
-
-    /// Transforms the `Option<T>` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)`
-    /// to `Err(err(v))`.
-    fn none_or_else<E, F>(self, err: F) -> Result<(), E>
-    where
-        F: FnOnce(Self::Inner) -> E;
-}
-
-impl<T> OptionExt for Option<T> {
-    type Inner = T;
-
-    fn none_or_else<E, F>(self, err: F) -> Result<(), E>
-    where
-        F: FnOnce(T) -> E,
-    {
-        match self {
-            Some(v) => Err(err(v)),
-            None => Ok(()),
-        }
-    }
-}
diff --git a/juniper_hyper/examples/hyper_server.rs b/juniper_hyper/examples/hyper_server.rs
index 588369863..912226498 100644
--- a/juniper_hyper/examples/hyper_server.rs
+++ b/juniper_hyper/examples/hyper_server.rs
@@ -5,7 +5,7 @@ use hyper::{
     Body, Method, Response, Server, StatusCode,
 };
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription, RootNode,
 };
 
diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs
index 409e8ef7c..7ead57fe1 100644
--- a/juniper_hyper/src/lib.rs
+++ b/juniper_hyper/src/lib.rs
@@ -319,7 +319,7 @@ mod tests {
     };
     use juniper::{
         http::tests as http_tests,
-        tests::fixtures::starwars::{model::Database, schema::Query},
+        tests::fixtures::starwars::schema::{Database, Query},
         EmptyMutation, EmptySubscription, RootNode,
     };
     use reqwest::{self, blocking::Response as ReqwestResponse};
diff --git a/juniper_iron/examples/iron_server.rs b/juniper_iron/examples/iron_server.rs
index ee86b0a06..74ce40548 100644
--- a/juniper_iron/examples/iron_server.rs
+++ b/juniper_iron/examples/iron_server.rs
@@ -8,7 +8,7 @@ use std::env;
 
 use iron::prelude::*;
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription,
 };
 use juniper_iron::{GraphQLHandler, GraphiQLHandler};
diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs
index 82077d62b..0e91c8a0e 100644
--- a/juniper_iron/src/lib.rs
+++ b/juniper_iron/src/lib.rs
@@ -421,7 +421,7 @@ mod tests {
 
     use juniper::{
         http::tests as http_tests,
-        tests::fixtures::starwars::{model::Database, schema::Query},
+        tests::fixtures::starwars::schema::{Database, Query},
         EmptyMutation, EmptySubscription,
     };
 
diff --git a/juniper_rocket/examples/rocket_server.rs b/juniper_rocket/examples/rocket_server.rs
index 8a95d7e40..0a55adadd 100644
--- a/juniper_rocket/examples/rocket_server.rs
+++ b/juniper_rocket/examples/rocket_server.rs
@@ -3,7 +3,7 @@
 use rocket::{response::content, State};
 
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription, RootNode,
 };
 
diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs
index 892695437..65c3b4dc6 100644
--- a/juniper_rocket/src/lib.rs
+++ b/juniper_rocket/src/lib.rs
@@ -141,8 +141,7 @@ impl GraphQLResponse {
     /// # use rocket::response::content;
     /// # use rocket::State;
     /// #
-    /// # use juniper::tests::fixtures::starwars::schema::Query;
-    /// # use juniper::tests::fixtures::starwars::model::Database;
+    /// # use juniper::tests::fixtures::starwars::schema::{Database, Query};
     /// # use juniper::{EmptyMutation, EmptySubscription, FieldError, RootNode, Value};
     /// #
     /// # type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
@@ -422,7 +421,7 @@ mod fromform_tests {
 mod tests {
     use juniper::{
         http::tests as http_tests,
-        tests::fixtures::starwars::{model::Database, schema::Query},
+        tests::fixtures::starwars::schema::{Database, Query},
         EmptyMutation, EmptySubscription, RootNode,
     };
     use rocket::{
diff --git a/juniper_rocket_async/examples/rocket_server.rs b/juniper_rocket_async/examples/rocket_server.rs
index a34899965..def21f336 100644
--- a/juniper_rocket_async/examples/rocket_server.rs
+++ b/juniper_rocket_async/examples/rocket_server.rs
@@ -1,5 +1,5 @@
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription, RootNode,
 };
 use rocket::{response::content, State};
diff --git a/juniper_rocket_async/src/lib.rs b/juniper_rocket_async/src/lib.rs
index c32b1e709..55b2c448b 100644
--- a/juniper_rocket_async/src/lib.rs
+++ b/juniper_rocket_async/src/lib.rs
@@ -160,8 +160,7 @@ impl GraphQLResponse {
     /// # use rocket::response::content;
     /// # use rocket::State;
     /// #
-    /// # use juniper::tests::fixtures::starwars::schema::Query;
-    /// # use juniper::tests::fixtures::starwars::model::Database;
+    /// # use juniper::tests::fixtures::starwars::schema::{Database, Query};
     /// # use juniper::{EmptyMutation, EmptySubscription, FieldError, RootNode, Value};
     /// #
     /// # type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
@@ -456,7 +455,7 @@ mod tests {
 
     use juniper::{
         http::tests as http_tests,
-        tests::fixtures::starwars::{model::Database, schema::Query},
+        tests::fixtures::starwars::schema::{Database, Query},
         EmptyMutation, EmptySubscription, RootNode,
     };
     use rocket::{
diff --git a/juniper_warp/examples/warp_server.rs b/juniper_warp/examples/warp_server.rs
index 9eb14e205..d0f1312c0 100644
--- a/juniper_warp/examples/warp_server.rs
+++ b/juniper_warp/examples/warp_server.rs
@@ -3,7 +3,7 @@
 use std::env;
 
 use juniper::{
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription, RootNode,
 };
 use warp::{http::Response, Filter};
diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs
index 495e72498..3d270143f 100644
--- a/juniper_warp/src/lib.rs
+++ b/juniper_warp/src/lib.rs
@@ -602,7 +602,7 @@ mod tests {
     #[tokio::test]
     async fn graphql_handler_works_json_post() {
         use juniper::{
-            tests::fixtures::starwars::{model::Database, schema::Query},
+            tests::fixtures::starwars::schema::{Database, Query},
             EmptyMutation, EmptySubscription, RootNode,
         };
 
@@ -641,7 +641,7 @@ mod tests {
     #[tokio::test]
     async fn batch_requests_work() {
         use juniper::{
-            tests::fixtures::starwars::{model::Database, schema::Query},
+            tests::fixtures::starwars::schema::{Database, Query},
             EmptyMutation, EmptySubscription, RootNode,
         };
 
@@ -696,7 +696,7 @@ mod tests_http_harness {
     use super::*;
     use juniper::{
         http::tests::{run_http_test_suite, HttpIntegration, TestResponse},
-        tests::fixtures::starwars::{model::Database, schema::Query},
+        tests::fixtures::starwars::schema::{Database, Query},
         EmptyMutation, EmptySubscription, RootNode,
     };
     use warp::{


Test patch:
diff --git a/docs/book/tests/Cargo.toml b/docs/book/tests/Cargo.toml
index 120f1a894..4cf997e47 100644
--- a/docs/book/tests/Cargo.toml
+++ b/docs/book/tests/Cargo.toml
@@ -12,7 +12,7 @@ juniper_subscriptions = { path = "../../../juniper_subscriptions" }
 
 derive_more = "0.99.7"
 futures = "0.3"
-tokio = { version = "0.2", features = ["rt-core", "blocking", "stream", "rt-util"] }
+tokio = { version = "0.2", features = ["blocking", "macros", "rt-core", "rt-util", "stream"] }
 iron = "0.5.0"
 mount = "0.4.0"
 
diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs
new file mode 100644
index 000000000..d7227c381
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs
@@ -0,0 +1,19 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[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, __num: i32) -> &str {
+        "funA"
+    }
+}
+
+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
new file mode 100644
index 000000000..f06c385cc
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr
@@ -0,0 +1,19 @@
+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.
+  --> $DIR/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
+ --> $DIR/argument_double_underscored.rs:4:18
+  |
+4 | #[graphql(impl = CharacterValue)]
+  |                  ^^^^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `Character` in this scope
+  --> $DIR/argument_double_underscored.rs:10:6
+   |
+10 | impl Character for ObjA {}
+   |      ^^^^^^^^^ not found in this scope
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
new file mode 100644
index 000000000..db6ad3002
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs
@@ -0,0 +1,21 @@
+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;
+}
+
+fn main() {}
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
new file mode 100644
index 000000000..c4e89fc48
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr
@@ -0,0 +1,16 @@
+error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
+  --> $DIR/argument_non_input_type.rs:16:1
+   |
+16 | #[graphql_interface(for = ObjA)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
+   |
+   = note: required by `juniper::marker::IsInputType::mark`
+   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
+  --> $DIR/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 an attribute macro (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
new file mode 100644
index 000000000..df2e5b85c
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs
@@ -0,0 +1,29 @@
+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_interface(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
new file mode 100644
index 000000000..29124c3ce
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr
@@ -0,0 +1,20 @@
+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`
+  --> $DIR/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_interface(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
+
+error[E0412]: cannot find type `CharacterValue` in this scope
+ --> $DIR/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
+  --> $DIR/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
new file mode 100644
index 000000000..c99d6dcfb
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs
@@ -0,0 +1,24 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[graphql_interface(for = Human)]
+trait Character {
+    fn id(&self) -> i32 {
+        0
+    }
+
+    #[graphql_interface(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
new file mode 100644
index 000000000..718a0a251
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr
@@ -0,0 +1,19 @@
+error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context`
+  --> $DIR/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
+  --> $DIR/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
+  --> $DIR/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
new file mode 100644
index 000000000..a9949a65d
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs
@@ -0,0 +1,24 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[graphql_interface(for = Human)]
+trait Character {
+    fn id(&self) -> i32 {
+        0
+    }
+
+    #[graphql_interface(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
new file mode 100644
index 000000000..8c4594950
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr
@@ -0,0 +1,19 @@
+error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only
+  --> $DIR/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
+  --> $DIR/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
+  --> $DIR/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
new file mode 100644
index 000000000..43115d327
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs
@@ -0,0 +1,19 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[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) -> &str {
+        "funA"
+    }
+}
+
+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
new file mode 100644
index 000000000..adebb09f2
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr
@@ -0,0 +1,19 @@
+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.
+  --> $DIR/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
+ --> $DIR/field_double_underscored.rs:4:18
+  |
+4 | #[graphql(impl = CharacterValue)]
+  |                  ^^^^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `Character` in this scope
+  --> $DIR/field_double_underscored.rs:10:6
+   |
+10 | impl Character for ObjA {}
+   |      ^^^^^^^^^ not found in this scope
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
new file mode 100644
index 000000000..a860ace35
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs
@@ -0,0 +1,24 @@
+use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject};
+
+#[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 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
new file mode 100644
index 000000000..73b266d3b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr
@@ -0,0 +1,8 @@
+error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
+  --> $DIR/field_non_output_return_type.rs:17:1
+   |
+17 | #[graphql_interface(for = ObjA)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
+   |
+   = note: required by `juniper::marker::IsOutputType::mark`
+   = note: this error originates in an attribute macro (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
new file mode 100644
index 000000000..f3ae4673e
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs
@@ -0,0 +1,24 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[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) -> &str {
+        "funA"
+    }
+
+    #[graphql_interface(name = "id")]
+    fn id2(&self) -> &str {
+        "funB"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr
new file mode 100644
index 000000000..db1333863
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr
@@ -0,0 +1,25 @@
+error: GraphQL interface must have a different name for each field
+  --> $DIR/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
+ --> $DIR/fields_duplicate.rs:4:18
+  |
+4 | #[graphql(impl = CharacterValue)]
+  |                  ^^^^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `Character` in this scope
+  --> $DIR/fields_duplicate.rs:10:6
+   |
+10 | impl Character for ObjA {}
+   |      ^^^^^^^^^ not found in this scope
diff --git a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs b/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs
deleted file mode 100644
index 1ab0e6e17..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-#[graphql(scalar = juniper::DefaultScalarValue)]
-pub struct ObjA {
-    test: String,
-}
-
-enum Character {
-    A(ObjA),
-}
-
-juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-    field id(__test: ObjA) -> &str {
-        match *self {
-            Character::A(_) => "funA",
-        }
-    }
-
-    instance_resolvers: |_| {
-        &ObjA => match *self { Character::A(ref h) => Some(h) },
-    }
-});
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr b/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr
deleted file mode 100644
index 10bf97ff0..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr
+++ /dev/null
@@ -1,27 +0,0 @@
-error[E0277]: the trait bound `ObjA: FromInputValue` is not satisfied
-  --> $DIR/impl_argument_no_object.rs:11:1
-   |
-11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-12 | |     field id(__test: ObjA) -> &str {
-13 | |         match *self {
-14 | |             Character::A(_) => "funA",
-...  |
-20 | |     }
-21 | | });
-   | |___^ the trait `FromInputValue` is not implemented for `ObjA`
-   |
-   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error[E0277]: the trait bound `ObjA: FromInputValue` is not satisfied
-  --> $DIR/impl_argument_no_object.rs:11:1
-   |
-11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-12 | |     field id(__test: ObjA) -> &str {
-13 | |         match *self {
-14 | |             Character::A(_) => "funA",
-...  |
-20 | |     }
-21 | | });
-   | |___^ the trait `FromInputValue` is not implemented for `ObjA`
-   |
-   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled
deleted file mode 100644
index 65f8117d0..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled
+++ /dev/null
@@ -1,23 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-#[graphql(scalar = juniper::DefaultScalarValue)]
-pub struct ObjA {
-    test: String,
-}
-
-enum Character {
-    A(ObjA),
-}
-
-juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-    field id(__test: String) -> &str {
-        match *self {
-            Character::A(_) => "funA",
-        }
-    }
-
-    instance_resolvers: |_| {
-        &ObjA => match *self { Character::A(ref h) => Some(h) },
-    }
-});
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled
deleted file mode 100644
index 4af462fa4..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled
+++ /dev/null
@@ -1,12 +0,0 @@
-enum Character {}
-
-juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
-    field id() -> &str {
-        match *self {
-        }
-    }
-
-    instance_resolvers: |_| {}
-});
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled
deleted file mode 100644
index e3075f8ac..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled
+++ /dev/null
@@ -1,23 +0,0 @@
-#[derive(juniper::GraphQLInputObject)]
-#[graphql(scalar = juniper::DefaultScalarValue)]
-pub struct ObjA {
-    test: String,
-}
-
-enum Character {
-    A(ObjA),
-}
-
-juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-    field id() -> &str {
-        match *self {
-            Character::A(_) => "funA",
-        }
-    }
-
-    instance_resolvers: |_| {
-        &ObjA => match *self { Character::A(ref h) => Some(h) },
-    }
-});
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled
deleted file mode 100644
index 1ff44b942..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled
+++ /dev/null
@@ -1,23 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-#[graphql(scalar = juniper::DefaultScalarValue)]
-pub struct ObjA {
-    test: String,
-}
-
-enum Character {
-    A(ObjA),
-}
-
-juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-    field __id() -> &str {
-        match *self {
-            Character::A(_) => "funA",
-        }
-    }
-
-    instance_resolvers: |_| {
-        &ObjA => match *self { Character::A(ref h) => Some(h) },
-    }
-});
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled
deleted file mode 100644
index c0b88b4b8..000000000
--- a/integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled
+++ /dev/null
@@ -1,29 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-#[graphql(scalar = juniper::DefaultScalarValue)]
-pub struct ObjA {
-    test: String,
-}
-
-enum Character {
-    A(ObjA),
-}
-
-juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
-    field id() -> &str {
-        match *self {
-            Character::A(_) => "funA",
-        }
-    }
-
-    field id() -> &str {
-        match *self {
-            Character::A(_) => "funA",
-        }
-    }
-
-    instance_resolvers: |_| {
-        &ObjA => match *self { Character::A(ref h) => Some(h) },
-    }
-});
-
-fn main() {}
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
new file mode 100644
index 000000000..0aba2508d
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 000000000..2de9329c7
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr
@@ -0,0 +1,17 @@
+error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied
+  --> $DIR/implementer_non_object_type.rs:15:1
+   |
+15 | #[graphql_interface(for = ObjA)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `ObjA`
+   |
+   = note: required by `juniper::marker::GraphQLObjectType::mark`
+   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied
+  --> $DIR/implementer_non_object_type.rs:15:1
+   |
+15 | #[graphql_interface(for = ObjA)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA`
+   |
+   = note: required by `juniper::marker::IsOutputType::mark`
+   = note: this error originates in an attribute macro (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
new file mode 100644
index 000000000..fbdc8f12e
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs
@@ -0,0 +1,21 @@
+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"
+    }
+}
+
+#[graphql_interface(for = [ObjA, ObjA])]
+trait Character {
+    fn id(&self) -> &str;
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr
new file mode 100644
index 000000000..40db273e9
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr
@@ -0,0 +1,17 @@
+error: duplicated attribute argument found
+  --> $DIR/implementers_duplicate_pretty.rs:16:34
+   |
+16 | #[graphql_interface(for = [ObjA, ObjA])]
+   |                                  ^^^^
+
+error[E0412]: cannot find type `CharacterValue` in this scope
+ --> $DIR/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
new file mode 100644
index 000000000..1acb56c01
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs
@@ -0,0 +1,23 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharacterValue)]
+pub struct ObjA {
+    test: 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;
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr
new file mode 100644
index 000000000..2a62e2eaa
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr
@@ -0,0 +1,21 @@
+error[E0119]: conflicting implementations of trait `<CharacterValue as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`:
+  --> $DIR/implementers_duplicate_ugly.rs:18:1
+   |
+18 | #[graphql_interface(for = [ObjA, ObjAlias])]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   | |
+   | first implementation here
+   | conflicting implementation for `ObjA`
+   |
+   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValue`:
+  --> $DIR/implementers_duplicate_ugly.rs:18:1
+   |
+18 | #[graphql_interface(for = [ObjA, ObjAlias])]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   | |
+   | first implementation here
+   | conflicting implementation for `CharacterValue`
+   |
+   = note: this error originates in an attribute macro (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
new file mode 100644
index 000000000..b233119f1
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs
@@ -0,0 +1,10 @@
+use juniper::graphql_interface;
+
+#[graphql_interface]
+trait __Character {
+    fn id(&self) -> &str {
+        "funA"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr
new file mode 100644
index 000000000..de8c6715f
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr
@@ -0,0 +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.
+ --> $DIR/name_double_underscored.rs:4:7
+  |
+4 | trait __Character {
+  |       ^^^^^^^^^^^
+  |
+  = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/no_fields.rs
new file mode 100644
index 000000000..115960359
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/no_fields.rs
@@ -0,0 +1,15 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[derive(GraphQLObject)]
+#[graphql(impl = CharacterValue)]
+pub struct ObjA {
+    test: String,
+}
+
+#[graphql_interface]
+impl Character for ObjA {}
+
+#[graphql_interface(for = ObjA)]
+trait Character {}
+
+fn main() {}
\ No newline at end of file
diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/no_fields.stderr
new file mode 100644
index 000000000..367a5e9bc
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/no_fields.stderr
@@ -0,0 +1,19 @@
+error: GraphQL interface must have at least one field
+  --> $DIR/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
+ --> $DIR/no_fields.rs:4:18
+  |
+4 | #[graphql(impl = CharacterValue)]
+  |                  ^^^^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `Character` in this scope
+  --> $DIR/no_fields.rs:10:6
+   |
+10 | impl Character for ObjA {}
+   |      ^^^^^^^^^ not found in this scope
diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.rs b/integration_tests/codegen_fail/fail/interface/wrong_item.rs
new file mode 100644
index 000000000..9b37b9429
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/wrong_item.rs
@@ -0,0 +1,11 @@
+use juniper::{graphql_interface, GraphQLObject};
+
+#[derive(GraphQLObject)]
+pub struct ObjA {
+    test: String,
+}
+
+#[graphql_interface(for = ObjA)]
+enum Character {}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item.stderr
new file mode 100644
index 000000000..8bb40afa5
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/interface/wrong_item.stderr
@@ -0,0 +1,7 @@
+error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only
+ --> $DIR/wrong_item.rs:8:1
+  |
+8 | #[graphql_interface(for = ObjA)]
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr
index f8c1a9f08..f703e72b9 100644
--- a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr
+++ b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr
@@ -1,4 +1,4 @@
-error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
+error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`:
  --> $DIR/enum_same_type_ugly.rs:3:10
   |
 3 | #[derive(GraphQLUnion)]
diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr b/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr
index fca94bfb8..9e3152bd2 100644
--- a/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr
+++ b/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr
@@ -1,4 +1,4 @@
-error: duplicated attribute
+error: duplicated attribute argument found
  --> $DIR/struct_same_type_pretty.rs:5:14
   |
 5 | #[graphql(on i32 = Character::b)]
diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr
index 08d350828..81bf349a8 100644
--- a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr
+++ b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr
@@ -1,4 +1,4 @@
-error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
+error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`:
  --> $DIR/struct_same_type_ugly.rs:3:10
   |
 3 | #[derive(GraphQLUnion)]
diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr
index f1cb64857..979c71421 100644
--- a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr
+++ b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr
@@ -1,4 +1,4 @@
-error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
+error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`:
  --> $DIR/trait_same_type_ugly.rs:3:1
   |
 3 | #[graphql_union]
diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml
index 893571478..7fedd6d81 100644
--- a/integration_tests/juniper_tests/Cargo.toml
+++ b/integration_tests/juniper_tests/Cargo.toml
@@ -10,6 +10,7 @@ futures = "0.3.1"
 juniper = { path = "../../juniper" }
 
 [dev-dependencies]
+async-trait = "0.1.39"
 serde_json = { version = "1" }
 fnv = "1.0.3"
 tokio = { version = "0.2", features = ["rt-core", "time", "macros"] }
\ No newline at end of file
diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs
new file mode 100644
index 000000000..3cf4f198d
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs
@@ -0,0 +1,5259 @@
+//! Tests for `#[graphql_interface]` macro.
+
+use juniper::{
+    execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation,
+    EmptySubscription, Executor, FieldError, FieldResult, GraphQLObject, GraphQLType,
+    IntoFieldError, RootNode, ScalarValue, Variables,
+};
+
+fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>, S>
+where
+    Q: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
+    S: ScalarValue + 'q,
+{
+    RootNode::new(
+        query_root,
+        EmptyMutation::<C>::new(),
+        EmptySubscription::<C>::new(),
+    )
+}
+
+mod no_implers {
+    use super::*;
+
+    #[graphql_interface]
+    trait Character {
+        fn id(&self) -> &str;
+    }
+
+    #[graphql_interface(dyn = DynHero)]
+    trait Hero {
+        fn info(&self) -> &str;
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            unimplemented!()
+        }
+
+        fn hero(&self) -> Box<DynHero<'_>> {
+            unimplemented!()
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_interface() {
+        let schema = schema(QueryRoot);
+
+        for interface in &["Character", "Hero"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        kind
+                    }}
+                }}"#,
+                interface,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_trait_name() {
+        let schema = schema(QueryRoot);
+
+        for interface in &["Character", "Hero"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        name
+                    }}
+                }}"#,
+                interface,
+            );
+
+            let expected_name: &str = *interface;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        let schema = schema(QueryRoot);
+
+        for interface in &["Character", "Hero"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        description
+                    }}
+                }}"#,
+                interface,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+            );
+        }
+    }
+}
+
+mod trivial {
+    use super::*;
+
+    #[graphql_interface(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>])]
+    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>])]
+    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, 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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": {"interfaces": [
+                        {"kind": "INTERFACE", "name": "Character"},
+                        {"kind": "INTERFACE", "name": "Hero"},
+                    ]}}),
+                    vec![],
+                )),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_trait_name() {
+        let schema = schema(QueryRoot::Human);
+
+        for interface in &["Character", "Hero"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        name
+                    }}
+                }}"#,
+                interface,
+            );
+
+            let expected_name: &str = *interface;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), 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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+            );
+        }
+    }
+}
+
+mod explicit_alias {
+    use super::*;
+
+    #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])]
+    trait Character {
+        fn id(&self) -> &str;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterEnum)]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterEnum)]
+    struct Droid {
+        id: String,
+        primary_function: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Droid {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterEnum {
+            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 resolves_human() {
+        const DOC: &str = r#"{
+            character {
+                ... on Human {
+                    humanId: id
+                    homePlanet
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_droid() {
+        const DOC: &str = r#"{
+            character {
+                ... on Droid {
+                    droidId: id
+                    primaryFunction
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn 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, &Variables::new(), &()).await,
+                Ok((graphql_value!({"character": {"id": expected_id}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn is_graphql_interface() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_trait_name() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+        );
+    }
+}
+
+mod trivial_async {
+    use super::*;
+
+    #[graphql_interface(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>])]
+    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>])]
+    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,
+        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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": {"interfaces": [
+                        {"kind": "INTERFACE", "name": "Character"},
+                        {"kind": "INTERFACE", "name": "Hero"},
+                    ]}}),
+                    vec![],
+                )),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_trait_name() {
+        let schema = schema(QueryRoot::Human);
+
+        for interface in &["Character", "Hero"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        name
+                    }}
+                }}"#,
+                interface,
+            );
+
+            let expected_name: &str = *interface;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), 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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+            );
+        }
+    }
+}
+
+mod explicit_async {
+    use super::*;
+
+    #[graphql_interface(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;
+    }
+
+    #[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
+        }
+
+        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 {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl Hero for Droid {
+        async fn id(&self) -> &str {
+            &self.id
+        }
+
+        fn info(&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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"hero": {"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", "None available"),
+        ] {
+            let schema = schema(*root);
+
+            let expected_id: &str = *expected_id;
+            let expected_info: &str = *expected_info;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"character": {
+                        "id": expected_id,
+                        "info": expected_info,
+                    }}),
+                    vec![],
+                )),
+            );
+        }
+    }
+
+    #[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, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"hero": {
+                        "id": expected_id,
+                        "info": expected_info,
+                    }}),
+                    vec![],
+                )),
+            );
+        }
+    }
+}
+
+mod fallible_field {
+    use super::*;
+
+    struct CustomError;
+
+    impl<S: ScalarValue> IntoFieldError<S> for CustomError {
+        fn into_field_error(self) -> FieldError<S> {
+            juniper::FieldError::new("Whatever", graphql_value!({"code": "some"}))
+        }
+    }
+
+    #[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)
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue, DynHero<__S>])]
+    struct Droid {
+        id: String,
+        primary_function: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Droid {
+        fn id(&self) -> Result<&str, CustomError> {
+            Ok(&self.id)
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl Hero for Droid {
+        fn info(&self) -> Result<&str, CustomError> {
+            Ok(&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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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
+                        kind
+                        fields {{
+                            name
+                            type {{
+                                kind
+                                ofType {{
+                                    name
+                                }}
+                            }}
+                        }}
+                    }}
+                }}"#,
+                interface,
+            );
+
+            let expected_name: &str = *interface;
+            let expected_field_name: &str = *field;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": {
+                        "name": expected_name,
+                        "kind": "INTERFACE",
+                        "fields": [{
+                            "name": expected_field_name,
+                            "type": {"kind": "NON_NULL", "ofType": {"name": "String"}},
+                        }]
+                    }}),
+                    vec![],
+                )),
+            );
+        }
+    }
+}
+
+mod generic {
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid])]
+    trait Character<A = (), B: ?Sized = ()> {
+        fn id(&self) -> &str;
+    }
+
+    #[graphql_interface(dyn = DynHero, for = [Human, Droid])]
+    trait Hero<A, B: ?Sized> {
+        fn info(&self) -> &str;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue, DynHero<u8, (), __S>])]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl<A, B: ?Sized> Character<A, B> for Human {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl<A, B: ?Sized> Hero<A, B> for Human {
+        fn info(&self) -> &str {
+            &self.home_planet
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue<(), u8>, DynHero<u8, (), __S>])]
+    struct Droid {
+        id: String,
+        primary_function: String,
+    }
+
+    #[graphql_interface]
+    impl<A, B: ?Sized> Character<A, B> for Droid {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl<A, B: ?Sized> Hero<A, B> for Droid {
+        fn info(&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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_, u8, ()>> {
+            let ch: Box<DynHero<'_, u8, ()>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])),
+            );
+        }
+    }
+}
+
+mod generic_async {
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid])]
+    trait Character<A = (), B: ?Sized = ()> {
+        async fn id(&self) -> &str;
+    }
+
+    #[graphql_interface(dyn = DynHero, for = [Human, Droid])]
+    trait Hero<A, B: ?Sized> {
+        async fn info(&self) -> &str;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue, DynHero<u8, (), __S>])]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl<A, B: ?Sized> Character<A, B> for Human {
+        async fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl<A, B: ?Sized> Hero<A, B> for Human {
+        async fn info(&self) -> &str {
+            &self.home_planet
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue<(), u8>, DynHero<u8, (), __S>])]
+    struct Droid {
+        id: String,
+        primary_function: String,
+    }
+
+    #[graphql_interface]
+    impl<A, B: ?Sized> Character<A, B> for Droid {
+        async fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl<A, B: ?Sized> Hero<A, B> for Droid {
+        async fn info(&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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_, u8, ()>> {
+            let ch: Box<DynHero<'_, u8, ()>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])),
+            );
+        }
+    }
+}
+
+mod generic_lifetime_async {
+    use super::*;
+
+    #[graphql_interface(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>])]
+    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 {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl<'me, A> Hero<'me, A> for Droid {
+        async fn info(&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(),
+            }
+        }
+
+        fn hero(&self) -> Box<DynHero<'_, '_, ()>> {
+            let ch: Box<DynHero<'_, '_, ()>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])),
+            );
+        }
+    }
+}
+
+mod argument {
+    use super::*;
+
+    #[graphql_interface(for = Human)]
+    trait Character {
+        fn id_wide(&self, is_number: bool) -> &str;
+    }
+
+    #[graphql_interface(dyn = DynHero, for = Human)]
+    trait Hero {
+        fn info_wide(&self, is_planet: bool) -> &str;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue, DynHero<__S>])]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        fn id_wide(&self, is_number: bool) -> &str {
+            if is_number {
+                &self.id
+            } else {
+                "none"
+            }
+        }
+    }
+
+    #[graphql_interface(dyn)]
+    impl Hero for Human {
+        fn info_wide(&self, is_planet: bool) -> &str {
+            if is_planet {
+                &self.home_planet
+            } else {
+                &self.id
+            }
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }
+            .into()
+        }
+
+        fn hero(&self) -> Box<DynHero<'_>> {
+            Box::new(Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            })
+        }
+    }
+
+    #[tokio::test]
+    async fn enum_resolves_id_field() {
+        let schema = schema(QueryRoot);
+
+        for (input, expected) in &[
+            ("{ character { idWide(isNumber: true) } }", "human-32"),
+            ("{ character { idWide(isNumber: false) } }", "none"),
+        ] {
+            let expected: &str = *expected;
+
+            assert_eq!(
+                execute(*input, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"character": {"idWide": expected}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn dyn_resolves_info_field() {
+        let schema = schema(QueryRoot);
+
+        for (input, expected) in &[
+            ("{ hero { infoWide(isPlanet: true) } }", "earth"),
+            ("{ hero { infoWide(isPlanet: false) } }", "human-32"),
+        ] {
+            let expected: &str = *expected;
+
+            assert_eq!(
+                execute(*input, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"hero": {"infoWide": 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_arg_name: &str = *arg;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": {"fields": [
+                        {"name": expected_field_name, "args": [{"name": expected_arg_name}]},
+                    ]}}),
+                    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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": { "fields": [{"args": [{"description": None}]}]}}),
+                    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,
+            );
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": { "fields": [{"args": [{"defaultValue": None}]}]}}),
+                    vec![],
+                )),
+            );
+        }
+    }
+}
+
+mod default_argument {
+    use super::*;
+
+    #[graphql_interface(for = Human)]
+    trait Character {
+        async fn id(
+            &self,
+            #[graphql_interface(default)] first: String,
+            #[graphql_interface(default = "second".to_string())] second: String,
+            #[graphql_interface(default = "t")] third: String,
+        ) -> String;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterValue)]
+    struct Human {
+        id: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        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(),
+            }
+            .into()
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        let schema = schema(QueryRoot);
+
+        for (input, expected) in &[
+            ("{ character { id } }", "|second&t"),
+            (r#"{ character { id(first: "first") } }"#, "first|second&t"),
+            (r#"{ character { id(second: "") } }"#, "|&t"),
+            (
+                r#"{ character { id(first: "first", second: "") } }"#,
+                "first|&t",
+            ),
+            (
+                r#"{ character { id(first: "first", second: "", third: "") } }"#,
+                "first|&",
+            ),
+        ] {
+            let expected: &str = *expected;
+
+            assert_eq!(
+                execute(*input, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"character": {"id": expected}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn has_defaults() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                fields {
+                    args {
+                        name
+                        defaultValue
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{"args": [
+                    {"name": "first", "defaultValue": r#""""#},
+                    {"name": "second", "defaultValue": r#""second""#},
+                    {"name": "third", "defaultValue": r#""t""#},
+                ]}]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    /// Rust docs.
+    #[graphql_interface(for = Human)]
+    trait Character {
+        /// Rust `id` docs.
+        fn id(&self) -> &str;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterValue)]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }
+            .into()
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_doc_comment_as_description() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                description
+                fields {
+                    description
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "Rust docs.", "fields": [{"description": "Rust `id` docs."}],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod deprecation_from_attr {
+    #![allow(deprecated)]
+
+    use super::*;
+
+    #[graphql_interface(for = Human)]
+    trait Character {
+        fn id(&self) -> &str;
+
+        #[deprecated]
+        fn a(&self) -> &str {
+            "a"
+        }
+
+        #[deprecated(note = "Use `id`.")]
+        fn b(&self) -> &str {
+            "b"
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterValue)]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }
+            .into()
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"{
+            character {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_fields() {
+        const DOC: &str = r#"{
+            character {
+                a
+                b
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![],)),
+        );
+    }
+
+    #[tokio::test]
+    async fn deprecates_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                fields(includeDeprecated: true) {
+                    name
+                    isDeprecated
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "isDeprecated": false},
+                    {"name": "a", "isDeprecated": true},
+                    {"name": "b", "isDeprecated": true},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn provides_deprecation_reason() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                fields(includeDeprecated: true) {
+                    name
+                    deprecationReason
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "deprecationReason": None},
+                    {"name": "a", "deprecationReason": None},
+                    {"name": "b", "deprecationReason": "Use `id`."},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_name_description_and_deprecation {
+    #![allow(deprecated)]
+
+    use super::*;
+
+    /// Rust docs.
+    #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)]
+    trait Character {
+        /// Rust `id` docs.
+        #[graphql_interface(name = "myId", desc = "My character ID.", deprecated = "Not used.")]
+        #[deprecated(note = "Should be omitted.")]
+        fn id(
+            &self,
+            #[graphql_interface(name = "myName", desc = "My argument.", default)] n: Option<String>,
+        ) -> &str;
+
+        #[graphql_interface(deprecated)]
+        #[deprecated(note = "Should be omitted.")]
+        fn a(&self) -> &str {
+            "a"
+        }
+
+        fn b(&self) -> &str {
+            "b"
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterValue)]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        fn id(&self, _: Option<String>) -> &str {
+            &self.id
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }
+            .into()
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            character {
+                myId
+                a
+                b
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_name() {
+        const DOC: &str = r#"{
+            __type(name: "MyChar") {
+                name
+                fields(includeDeprecated: true) {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "MyChar",
+                    "fields": [
+                        {"name": "myId", "args": [{"name": "myName"}]},
+                        {"name": "a", "args": []},
+                        {"name": "b", "args": []},
+                    ],
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_description() {
+        const DOC: &str = r#"{
+            __type(name: "MyChar") {
+                description
+                fields(includeDeprecated: true) {
+                    name
+                    description
+                    args {
+                        description
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "My character.",
+                    "fields": [{
+                        "name": "myId",
+                        "description": "My character ID.",
+                        "args": [{"description": "My argument."}],
+                    }, {
+                        "name": "a",
+                        "description": None,
+                        "args": [],
+                    }, {
+                        "name": "b",
+                        "description": None,
+                        "args": [],
+                    }],
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_deprecation() {
+        const DOC: &str = r#"{
+            __type(name: "MyChar") {
+                fields(includeDeprecated: true) {
+                    name
+                    isDeprecated
+                    deprecationReason
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "fields": [{
+                        "name": "myId",
+                        "isDeprecated": true,
+                        "deprecationReason": "Not used.",
+                    }, {
+                        "name": "a",
+                        "isDeprecated": true,
+                        "deprecationReason": None,
+                    }, {
+                        "name": "b",
+                        "isDeprecated": false,
+                        "deprecationReason": None,
+                    }],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_scalar {
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid])]
+    #[graphql_interface(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)]
+    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)]
+    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,
+        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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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::<_, DefaultScalarValue, _>(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(*root);
+
+            let expected_id: &str = *expected_id;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(*root);
+
+            let expected_info: &str = *expected_info;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])),
+            );
+        }
+    }
+}
+
+mod custom_scalar {
+    use crate::custom_scalar::MyScalarValue;
+
+    use super::*;
+
+    #[graphql_interface(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)]
+    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)]
+    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,
+        Droid,
+    }
+
+    #[graphql_object(scalar = MyScalarValue)]
+    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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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::<_, MyScalarValue, _>(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(*root);
+
+            let expected_id: &str = *expected_id;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(*root);
+
+            let expected_info: &str = *expected_info;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])),
+            );
+        }
+    }
+}
+
+mod explicit_generic_scalar {
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid], scalar = S)]
+    trait Character<S: ScalarValue = DefaultScalarValue> {
+        fn id(&self) -> FieldResult<&str, S>;
+    }
+
+    #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)]
+    trait Hero<S: ScalarValue = DefaultScalarValue> {
+        async fn info(&self) -> FieldResult<&str, S>;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface(scalar = S)]
+    impl<S: ScalarValue> Character<S> for Human {
+        fn id(&self) -> FieldResult<&str, S> {
+            Ok(&self.id)
+        }
+    }
+
+    #[graphql_interface(dyn, scalar = S)]
+    impl<S: ScalarValue> Hero<S> for Human {
+        async fn info(&self) -> FieldResult<&str, S> {
+            Ok(&self.home_planet)
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])]
+    struct Droid {
+        id: String,
+        primary_function: String,
+    }
+
+    #[graphql_interface(scalar = S)]
+    impl<S: ScalarValue> Character<S> for Droid {
+        fn id(&self) -> FieldResult<&str, S> {
+            Ok(&self.id)
+        }
+    }
+
+    #[graphql_interface(dyn, scalar = S)]
+    impl<S: ScalarValue> Hero<S> for Droid {
+        async fn info(&self) -> FieldResult<&str, S> {
+            Ok(&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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])),
+            );
+        }
+    }
+}
+
+mod explicit_custom_context {
+    use super::*;
+
+    pub struct CustomContext;
+
+    impl juniper::Context for CustomContext {}
+
+    #[graphql_interface(for = [Human, Droid], context = CustomContext)]
+    trait Character {
+        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_interface(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_interface(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 {
+            &self.id
+        }
+
+        async fn info<'b>(&'b self, _: &()) -> &'b 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 {
+            &self.home_planet
+        }
+
+        fn more(&self, _: &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 {
+            &self.id
+        }
+
+        async fn info<'b>(&'b self, _: &()) -> &'b 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 {
+            &self.primary_function
+        }
+
+        fn more(&self, _: &CustomContext) -> &'static str {
+            "droid"
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object(context = CustomContext)]
+    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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &CustomContext).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, &Variables::new(), &CustomContext).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, &Variables::new(), &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
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &CustomContext).await,
+            Ok((
+                graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        for interface in &["character", "hero"] {
+            let doc = format!(
+                r#"{{
+                    {} {{
+                        id
+                        info
+                        more
+                    }}
+                }}"#,
+                interface,
+            );
+
+            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, &Variables::new(), &CustomContext).await,
+                    Ok((
+                        graphql_value!({expected_interface: {
+                            "id": expected_id,
+                            "info": expected_info,
+                            "more": expexted_more,
+                        }}),
+                        vec![],
+                    )),
+                );
+            }
+        }
+    }
+}
+
+mod inferred_custom_context_from_field {
+    use super::*;
+
+    pub struct CustomContext(String);
+
+    impl juniper::Context for CustomContext {}
+
+    #[graphql_interface(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 {
+        fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str {
+            &ctx.0
+        }
+
+        fn info<'b>(&'b self, _: &()) -> &'b 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 {
+            &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 {
+        fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str {
+            &ctx.0
+        }
+
+        fn info<'b>(&'b self, _: &()) -> &'b 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 {
+            &self.primary_function
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object(context = CustomContext)]
+    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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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 ctx = CustomContext("in-ctx".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).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);
+        let ctx = CustomContext("in-droid".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).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 ctx = CustomContext("in-ctx".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).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 ctx = CustomContext("in-droid".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).await,
+            Ok((
+                graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                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_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, &Variables::new(), &ctx).await,
+                    Ok((
+                        graphql_value!({expected_interface: {
+                            "id": expected_id,
+                            "info": expected_info,
+                        }}),
+                        vec![],
+                    )),
+                );
+            }
+        }
+    }
+}
+
+mod inferred_custom_context_from_downcast {
+    use super::*;
+
+    struct Database {
+        droid: Option<Droid>,
+    }
+
+    impl juniper::Context for Database {}
+
+    #[graphql_interface(for = [Human, Droid])]
+    trait Character {
+        #[graphql_interface(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_interface(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_interface(dyn)]
+    impl Hero for Droid {
+        fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> {
+            db.droid.as_ref()
+        }
+
+        async fn info(&self) -> &str {
+            &self.primary_function
+        }
+    }
+
+    #[derive(Clone)]
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object(context = Database)]
+    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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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 { droid: None };
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &db).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);
+        let db = Database {
+            droid: Some(Droid {
+                id: "droid-88".to_string(),
+                primary_function: "sit".to_string(),
+            }),
+        };
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &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, &Variables::new(), &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, &Variables::new(), &db).await,
+            Ok((
+                graphql_value!({"hero": {"droidId": "droid-88", "primaryFunction": "sit"}}),
+                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 db = Database { droid: None };
+
+            let expected_id: &str = *expected_id;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &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 expected_info: &str = *expected_info;
+            assert_eq!(
+                execute(DOC, None, &schema, &Variables::new(), &db).await,
+                Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])),
+            );
+        }
+    }
+}
+
+mod executor {
+    use juniper::LookAheadMethods as _;
+
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid], scalar = S)]
+    trait Character<S: ScalarValue> {
+        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,
+            #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>,
+        ) -> &'b str
+        where
+            S: Send + Sync;
+    }
+
+    #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)]
+    trait Hero<S: ScalarValue> {
+        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,
+            #[graphql_interface(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<S: ScalarValue> Character<S> for Human {
+        async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+        where
+            S: Send + Sync,
+        {
+            &self.home_planet
+        }
+    }
+
+    #[graphql_interface(dyn, scalar = S)]
+    impl<S: ScalarValue> Hero<S> for Human {
+        async fn info<'b>(&'b self, _: &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<S: ScalarValue> Character<S> for Droid {
+        async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+        where
+            S: Send + Sync,
+        {
+            &self.primary_function
+        }
+    }
+
+    #[graphql_interface(dyn, scalar = S)]
+    impl<S: ScalarValue> Hero<S> for Droid {
+        async fn info<'b>(&'b self, _: &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<DefaultScalarValue> {
+            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<DynHero<'_, DefaultScalarValue>> {
+            let ch: Box<DynHero<'_, _>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                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, &Variables::new(), &()).await,
+                    Ok((
+                        graphql_value!({expected_interface: {"id": "id", "info": expected_info}}),
+                        vec![],
+                    )),
+                );
+            }
+        }
+    }
+}
+
+mod ignored_method {
+    use super::*;
+
+    #[graphql_interface(for = Human)]
+    trait Character {
+        fn id(&self) -> &str;
+
+        #[graphql_interface(ignore)]
+        fn ignored(&self) -> Option<&Human> {
+            None
+        }
+
+        #[graphql_interface(skip)]
+        fn skipped(&self) {}
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = CharacterValue)]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface]
+    impl Character for Human {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }
+            .into()
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human() {
+        const DOC: &str = r#"{
+            character {
+                ... on Human {
+                    humanId: id
+                    homePlanet
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"{
+            character {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_not_field() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{"name": "id"}]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod downcast_method {
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid])]
+    trait Character {
+        fn id(&self) -> &str;
+
+        #[graphql_interface(downcast)]
+        fn as_human(&self) -> Option<&Human> {
+            None
+        }
+    }
+
+    #[graphql_interface(dyn = DynHero, for = [Human, Droid])]
+    trait Hero {
+        fn info(&self) -> &str;
+
+        #[graphql_interface(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]
+    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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": {"fields": [{"name": expected_field}]}}),
+                    vec![],
+                )),
+            );
+        }
+    }
+}
+
+mod external_downcast {
+    use super::*;
+
+    struct Database {
+        human: Option<Human>,
+        droid: Option<Droid>,
+    }
+
+    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)]
+    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<DynHero<'_>> {
+            let ch: Box<DynHero<'_>> = 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, &Variables::new(), &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, &Variables::new(), &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, &Variables::new(), &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, &Variables::new(), &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, &Variables::new(), &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, &Variables::new(), &db).await,
+                Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])),
+            );
+        }
+    }
+}
diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs
index 99d2b3651..37e12c31f 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;
 mod derive_object_with_raw_idents;
 mod impl_object;
 mod impl_scalar;
+mod interface_attr;
 mod scalar_value_transparent;
 mod union_attr;
 mod union_derive;
diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs
index 2581512cd..ebad82217 100644
--- a/integration_tests/juniper_tests/src/codegen/union_attr.rs
+++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs
@@ -305,7 +305,7 @@ mod generic {
     }
 }
 
-mod description_from_doc_comments {
+mod description_from_doc_comment {
     use super::*;
 
     /// Rust docs.
@@ -647,27 +647,27 @@ mod custom_scalar {
     }
 }
 
-mod inferred_custom_context {
+mod explicit_custom_context {
     use super::*;
 
-    #[graphql_union]
+    #[graphql_union(context = CustomContext)]
     trait Character {
-        fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
+        fn as_human(&self) -> Option<&HumanCustomContext> {
             None
         }
-        fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
+        fn as_droid(&self) -> Option<&DroidCustomContext> {
             None
         }
     }
 
     impl Character for HumanCustomContext {
-        fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
+        fn as_human(&self) -> Option<&HumanCustomContext> {
             Some(&self)
         }
     }
 
     impl Character for DroidCustomContext {
-        fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
+        fn as_droid(&self) -> Option<&DroidCustomContext> {
             Some(&self)
         }
     }
@@ -734,27 +734,27 @@ mod inferred_custom_context {
     }
 }
 
-mod explicit_custom_context {
+mod inferred_custom_context {
     use super::*;
 
-    #[graphql_union(context = CustomContext)]
+    #[graphql_union]
     trait Character {
-        fn as_human(&self) -> Option<&HumanCustomContext> {
+        fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
             None
         }
-        fn as_droid(&self) -> Option<&DroidCustomContext> {
+        fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
             None
         }
     }
 
     impl Character for HumanCustomContext {
-        fn as_human(&self) -> Option<&HumanCustomContext> {
+        fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
             Some(&self)
         }
     }
 
     impl Character for DroidCustomContext {
-        fn as_droid(&self) -> Option<&DroidCustomContext> {
+        fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
             Some(&self)
         }
     }
@@ -821,7 +821,7 @@ mod explicit_custom_context {
     }
 }
 
-mod ignored_methods {
+mod ignored_method {
     use super::*;
 
     #[graphql_union]
diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs
index aacb341f5..eeb4e144d 100644
--- a/juniper/src/executor_tests/introspection/mod.rs
+++ b/juniper/src/executor_tests/introspection/mod.rs
@@ -7,6 +7,7 @@ use self::input_object::{NamedPublic, NamedPublicWithDescription};
 
 use crate::{
     executor::Variables,
+    graphql_interface, graphql_object,
     schema::model::RootNode,
     types::scalars::{EmptyMutation, EmptySubscription},
     value::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, Value},
@@ -22,10 +23,6 @@ enum Sample {
 
 struct Scalar(i32);
 
-struct Interface;
-
-struct Root;
-
 #[crate::graphql_scalar(name = "SampleScalar")]
 impl GraphQLScalar for Scalar {
     fn resolve(&self) -> Value {
@@ -41,23 +38,19 @@ impl GraphQLScalar for Scalar {
     }
 }
 
-graphql_interface!(Interface: () as "SampleInterface" |&self| {
-    description: "A sample interface"
-
-    field sample_enum() -> Sample as "A sample field in the interface" {
+/// A sample interface
+#[graphql_interface(name = "SampleInterface", for = Root, scalar = DefaultScalarValue)]
+trait Interface {
+    /// A sample field in the interface
+    fn sample_enum(&self) -> Sample {
         Sample::One
     }
+}
 
-    instance_resolvers: |&_| {
-        Root => Some(Root),
-    }
-});
+struct Root;
 
 /// The root query object in the schema
-#[crate::graphql_object(
-    interfaces = [&Interface]
-    Scalar = crate::DefaultScalarValue,
-)]
+#[graphql_object(interfaces = InterfaceValue)]
 impl Root {
     fn sample_enum() -> Sample {
         Sample::One
@@ -65,7 +58,7 @@ impl Root {
 
     #[graphql(arguments(
         first(description = "The first number",),
-        second(description = "The second number", default = 123,),
+        second(description = "The second number", default = 123),
     ))]
 
     /// A sample scalar field on the object
@@ -74,6 +67,9 @@ impl Root {
     }
 }
 
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl Interface for Root {}
+
 #[tokio::test]
 async fn test_execution() {
     let doc = r#"
diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs
index cffa7dcf4..1104454fa 100644
--- a/juniper/src/macros/tests/field.rs
+++ b/juniper/src/macros/tests/field.rs
@@ -1,15 +1,3 @@
-use crate::{
-    ast::InputValue,
-    executor::FieldResult,
-    schema::model::RootNode,
-    types::scalars::{EmptyMutation, EmptySubscription},
-    value::{DefaultScalarValue, Object, Value},
-};
-
-struct Interface;
-#[derive(Debug)]
-struct Root;
-
 /*
 
 Syntax to validate:
@@ -22,9 +10,21 @@ Syntax to validate:
 
 */
 
-#[crate::graphql_object(
-    interfaces = [&Interface],
-)]
+#![allow(deprecated)]
+
+use crate::{
+    ast::InputValue,
+    executor::FieldResult,
+    graphql_interface, graphql_object,
+    schema::model::RootNode,
+    types::scalars::{EmptyMutation, EmptySubscription},
+    value::{DefaultScalarValue, Object, Value},
+};
+
+#[derive(Debug)]
+struct Root;
+
+#[graphql_object(interfaces = [InterfaceValue])]
 impl Root {
     fn simple() -> i32 {
         0
@@ -104,44 +104,85 @@ impl Root {
     }
 }
 
-graphql_interface!(Interface: () |&self| {
-    field simple() -> i32 { 0 }
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl Interface for Root {
+    fn simple(&self) -> i32 {
+        0
+    }
 
-    field description() -> i32 as "Field description" { 0 }
+    fn description(&self) -> i32 {
+        0
+    }
 
-    field deprecated "Deprecation reason"
-        deprecated() -> i32 { 0 }
+    fn deprecated(&self) -> i32 {
+        0
+    }
 
-    field deprecated "Deprecation reason"
-        deprecated_descr() -> i32 as "Field description" { 0 }
+    fn deprecated_descr(&self) -> i32 {
+        0
+    }
+
+    fn attr_description(&self) -> i32 {
+        0
+    }
+
+    fn attr_description_collapse(&self) -> i32 {
+        0
+    }
+
+    fn attr_description_long(&self) -> i32 {
+        0
+    }
+
+    fn attr_deprecated(&self) -> i32 {
+        0
+    }
+
+    fn attr_deprecated_reason(&self) -> i32 {
+        0
+    }
+
+    fn attr_deprecated_descr(&self) -> i32 {
+        0
+    }
+}
+
+#[graphql_interface(for = Root, scalar = DefaultScalarValue)]
+trait Interface {
+    fn simple(&self) -> i32;
+
+    #[graphql_interface(desc = "Field description")]
+    fn description(&self) -> i32;
+
+    #[graphql_interface(deprecated = "Deprecation reason")]
+    fn deprecated(&self) -> i32;
+
+    #[graphql_interface(desc = "Field description", deprecated = "Deprecation reason")]
+    fn deprecated_descr(&self) -> i32;
 
     /// Field description
-    field attr_description() -> i32 { 0 }
+    fn attr_description(&self) -> i32;
 
     /// Field description
     /// with `collapse_docs` behavior
-    field attr_description_collapse() -> i32 { 0 }
+    fn attr_description_collapse(&self) -> i32;
 
     /// Get the i32 representation of 0.
     ///
     /// - This comment is longer.
     /// - These two lines are rendered as bullets by GraphiQL.
-    field attr_description_long() -> i32 { 0 }
+    fn attr_description_long(&self) -> i32;
 
     #[deprecated]
-    field attr_deprecated() -> i32 { 0 }
+    fn attr_deprecated(&self) -> i32;
 
     #[deprecated(note = "Deprecation reason")]
-    field attr_deprecated_reason() -> i32 { 0 }
+    fn attr_deprecated_reason(&self) -> i32;
 
     /// Field description
     #[deprecated(note = "Deprecation reason")]
-    field attr_deprecated_descr() -> i32 { 0 }
-
-    instance_resolvers: |&_| {
-        Root => Some(Root {}),
-    }
-});
+    fn attr_deprecated_descr(&self) -> i32;
+}
 
 async fn run_field_info_query<F>(type_name: &str, field_name: &str, f: F)
 where
diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs
index d802a9d8d..de56473c2 100644
--- a/juniper/src/macros/tests/impl_subscription.rs
+++ b/juniper/src/macros/tests/impl_subscription.rs
@@ -17,7 +17,7 @@ struct WithLifetime<'a> {
     value: &'a str,
 }
 
-#[crate::graphql_object(Context=Context)]
+#[crate::graphql_object(Context = Context)]
 impl<'a> WithLifetime<'a> {
     fn value(&'a self) -> &'a str {
         self.value
@@ -26,7 +26,7 @@ impl<'a> WithLifetime<'a> {
 
 struct WithContext;
 
-#[crate::graphql_object(Context=Context)]
+#[crate::graphql_object(Context = Context)]
 impl WithContext {
     fn ctx(ctx: &Context) -> bool {
         ctx.flag1
diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs
index 3e45f3cb7..fffeded65 100644
--- a/juniper/src/macros/tests/interface.rs
+++ b/juniper/src/macros/tests/interface.rs
@@ -1,12 +1,3 @@
-use std::marker::PhantomData;
-
-use crate::{
-    ast::InputValue,
-    schema::model::RootNode,
-    types::scalars::{EmptyMutation, EmptySubscription},
-    value::{DefaultScalarValue, Object, Value},
-};
-
 /*
 
 Syntax to validate:
@@ -19,135 +10,86 @@ Syntax to validate:
 
 */
 
-struct Concrete;
-
-struct CustomName;
-
-#[allow(dead_code)]
-struct WithLifetime<'a> {
-    data: PhantomData<&'a i32>,
-}
-
-#[allow(dead_code)]
-struct WithGenerics<T> {
-    data: T,
-}
-
-struct DescriptionFirst;
-struct FieldsFirst;
-struct InterfacesFirst;
-
-struct CommasWithTrailing;
-struct CommasOnMeta;
-
-struct ResolversWithTrailingComma;
+use crate::{
+    ast::InputValue,
+    graphql_interface, graphql_object,
+    schema::model::RootNode,
+    types::scalars::{EmptyMutation, EmptySubscription},
+    value::{DefaultScalarValue, Object, Value},
+};
 
-struct Root;
+struct Concrete;
 
-#[crate::graphql_object]
+#[graphql_object(impl = [
+    CustomNameValue, DescriptionValue, WithLifetimeValue<'_>, WithGenericsValue<()>,
+])]
 impl Concrete {
     fn simple() -> i32 {
         0
     }
 }
 
-graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| {
-    field simple() -> i32 { 0 }
-
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-});
-
-graphql_interface!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
-    field simple() -> i32 { 0 }
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-});
-
-graphql_interface!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
-    field simple() -> i32 { 0 }
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-});
-
-graphql_interface!(DescriptionFirst: () |&self| {
-    description: "A description"
-
-    field simple() -> i32 { 0 }
-
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-});
-
-graphql_interface!(FieldsFirst: () |&self| {
-    field simple() -> i32 { 0 }
-
-    description: "A description"
-
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-});
-
-graphql_interface!(InterfacesFirst: () |&self| {
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-
-    field simple() -> i32 { 0 }
-
-    description: "A description"
-});
-
-graphql_interface!(CommasWithTrailing: () |&self| {
-    instance_resolvers: |_| { Concrete => Some(Concrete) },
-
-    field simple() -> i32 { 0 },
-
-    description: "A description",
-});
-
-graphql_interface!(CommasOnMeta: () |&self| {
-    instance_resolvers: |_| { Concrete => Some(Concrete) }
-    description: "A description",
-
-    field simple() -> i32 { 0 }
-});
-
-graphql_interface!(ResolversWithTrailingComma: () |&self| {
-    instance_resolvers: |_| { Concrete => Some(Concrete), }
-    description: "A description",
-
-    field simple() -> i32 { 0 }
-});
-
-#[crate::graphql_object(
-    // FIXME: make async work
-    noasync
-)]
-impl<'a> Root {
-    fn custom_name() -> CustomName {
-        CustomName {}
+#[graphql_interface(for = Concrete, name = "ACustomNamedInterface", scalar = DefaultScalarValue)]
+trait CustomName {
+    fn simple(&self) -> i32;
+}
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl CustomName for Concrete {
+    fn simple(&self) -> i32 {
+        0
     }
+}
 
-    fn with_lifetime() -> WithLifetime<'a> {
-        WithLifetime { data: PhantomData }
-    }
-    fn with_generics() -> WithGenerics<i32> {
-        WithGenerics { data: 123 }
+#[graphql_interface(for = Concrete, scalar = DefaultScalarValue)]
+trait WithLifetime<'a> {
+    fn simple(&self) -> i32;
+}
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl<'a> WithLifetime<'a> for Concrete {
+    fn simple(&self) -> i32 {
+        0
     }
+}
 
-    fn description_first() -> DescriptionFirst {
-        DescriptionFirst {}
+#[graphql_interface(for = Concrete, scalar = DefaultScalarValue)]
+trait WithGenerics<T> {
+    fn simple(&self) -> i32;
+}
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl<T> WithGenerics<T> for Concrete {
+    fn simple(&self) -> i32 {
+        0
     }
-    fn fields_first() -> FieldsFirst {
-        FieldsFirst {}
+}
+
+#[graphql_interface(for = Concrete, desc = "A description", scalar = DefaultScalarValue)]
+trait Description {
+    fn simple(&self) -> i32;
+}
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl Description for Concrete {
+    fn simple(&self) -> i32 {
+        0
     }
-    fn interfaces_first() -> InterfacesFirst {
-        InterfacesFirst {}
+}
+
+struct Root;
+
+#[crate::graphql_object]
+impl Root {
+    fn custom_name() -> CustomNameValue {
+        Concrete.into()
     }
 
-    fn commas_with_trailing() -> CommasWithTrailing {
-        CommasWithTrailing {}
+    fn with_lifetime() -> WithLifetimeValue<'static> {
+        Concrete.into()
     }
-    fn commas_on_meta() -> CommasOnMeta {
-        CommasOnMeta {}
+    fn with_generics() -> WithGenericsValue<i32> {
+        Concrete.into()
     }
 
-    fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
-        ResolversWithTrailingComma {}
+    fn description() -> DescriptionValue {
+        Concrete.into()
     }
 }
 
@@ -256,115 +198,10 @@ async fn introspect_with_generics() {
 
 #[tokio::test]
 async fn introspect_description_first() {
-    run_type_info_query("DescriptionFirst", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("DescriptionFirst"))
-        );
-        assert_eq!(
-            object.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_fields_first() {
-    run_type_info_query("FieldsFirst", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("FieldsFirst"))
-        );
-        assert_eq!(
-            object.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interfaces_first() {
-    run_type_info_query("InterfacesFirst", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("InterfacesFirst"))
-        );
-        assert_eq!(
-            object.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_commas_with_trailing() {
-    run_type_info_query("CommasWithTrailing", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("CommasWithTrailing"))
-        );
-        assert_eq!(
-            object.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_commas_on_meta() {
-    run_type_info_query("CommasOnMeta", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("CommasOnMeta"))
-        );
-        assert_eq!(
-            object.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_resolvers_with_trailing_comma() {
-    run_type_info_query("ResolversWithTrailingComma", |object, fields| {
+    run_type_info_query("Description", |object, fields| {
         assert_eq!(
             object.get_field_value("name"),
-            Some(&Value::scalar("ResolversWithTrailingComma"))
+            Some(&Value::scalar("Description"))
         );
         assert_eq!(
             object.get_field_value("description"),
diff --git a/juniper/src/tests/fixtures/starwars/mod.rs b/juniper/src/tests/fixtures/starwars/mod.rs
index 2e271ab15..7207bd96a 100644
--- a/juniper/src/tests/fixtures/starwars/mod.rs
+++ b/juniper/src/tests/fixtures/starwars/mod.rs
@@ -1,4 +1,3 @@
-pub mod model;
 pub mod schema;
 #[cfg(feature = "schema-language")]
 pub mod schema_language;
diff --git a/juniper/src/tests/fixtures/starwars/model.rs b/juniper/src/tests/fixtures/starwars/model.rs
deleted file mode 100644
index ae250607a..000000000
--- a/juniper/src/tests/fixtures/starwars/model.rs
+++ /dev/null
@@ -1,319 +0,0 @@
-#![allow(missing_docs)]
-
-use std::collections::HashMap;
-
-use crate::GraphQLEnum;
-
-#[derive(GraphQLEnum, Copy, Clone, Eq, PartialEq, Debug)]
-pub enum Episode {
-    #[graphql(name = "NEW_HOPE")]
-    NewHope,
-    Empire,
-    Jedi,
-}
-
-pub trait Character {
-    fn id(&self) -> &str;
-    fn name(&self) -> &str;
-    fn friend_ids(&self) -> &[String];
-    fn appears_in(&self) -> &[Episode];
-    fn secret_backstory(&self) -> &Option<String>;
-    fn as_character(&self) -> &dyn Character;
-}
-
-pub trait Human: Character {
-    fn home_planet(&self) -> &Option<String>;
-}
-
-pub trait Droid: Character {
-    fn primary_function(&self) -> &Option<String>;
-}
-
-#[derive(Clone)]
-struct HumanData {
-    id: String,
-    name: String,
-    friend_ids: Vec<String>,
-    appears_in: Vec<Episode>,
-    secret_backstory: Option<String>,
-    home_planet: Option<String>,
-}
-
-#[derive(Clone)]
-struct DroidData {
-    id: String,
-    name: String,
-    friend_ids: Vec<String>,
-    appears_in: Vec<Episode>,
-    secret_backstory: Option<String>,
-    primary_function: Option<String>,
-}
-
-impl Character for HumanData {
-    fn id(&self) -> &str {
-        &self.id
-    }
-    fn name(&self) -> &str {
-        &self.name
-    }
-    fn friend_ids(&self) -> &[String] {
-        &self.friend_ids
-    }
-    fn appears_in(&self) -> &[Episode] {
-        &self.appears_in
-    }
-    fn secret_backstory(&self) -> &Option<String> {
-        &self.secret_backstory
-    }
-    fn as_character(&self) -> &dyn Character {
-        self
-    }
-}
-
-impl Human for HumanData {
-    fn home_planet(&self) -> &Option<String> {
-        &self.home_planet
-    }
-}
-
-impl Character for DroidData {
-    fn id(&self) -> &str {
-        &self.id
-    }
-    fn name(&self) -> &str {
-        &self.name
-    }
-    fn friend_ids(&self) -> &[String] {
-        &self.friend_ids
-    }
-    fn appears_in(&self) -> &[Episode] {
-        &self.appears_in
-    }
-    fn secret_backstory(&self) -> &Option<String> {
-        &self.secret_backstory
-    }
-    fn as_character(&self) -> &dyn Character {
-        self
-    }
-}
-
-impl Droid for DroidData {
-    fn primary_function(&self) -> &Option<String> {
-        &self.primary_function
-    }
-}
-
-#[derive(Default, Clone)]
-pub struct Database {
-    humans: HashMap<String, HumanData>,
-    droids: HashMap<String, DroidData>,
-}
-
-use crate::{
-    executor::Registry,
-    schema::meta::MetaType,
-    types::base::{GraphQLType, GraphQLValue},
-    value::ScalarValue,
-};
-
-impl<S> GraphQLType<S> for Database
-where
-    S: ScalarValue,
-{
-    fn name(_: &()) -> Option<&str> {
-        Some("_Database")
-    }
-
-    fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S>
-    where
-        S: 'r,
-    {
-        registry.build_object_type::<Self>(&(), &[]).into_meta()
-    }
-}
-
-impl<S> GraphQLValue<S> for Database
-where
-    S: ScalarValue,
-{
-    type Context = Self;
-    type TypeInfo = ();
-
-    fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
-        <Self as GraphQLType<S>>::name(info)
-    }
-}
-
-impl HumanData {
-    pub fn new(
-        id: &str,
-        name: &str,
-        friend_ids: &[&str],
-        appears_in: &[Episode],
-        secret_backstory: Option<&str>,
-        home_planet: Option<&str>,
-    ) -> HumanData {
-        HumanData {
-            id: id.to_owned(),
-            name: name.to_owned(),
-            friend_ids: friend_ids
-                .to_owned()
-                .into_iter()
-                .map(|f| f.to_owned())
-                .collect(),
-            appears_in: appears_in.to_vec(),
-            secret_backstory: secret_backstory.map(|b| b.to_owned()),
-            home_planet: home_planet.map(|p| p.to_owned()),
-        }
-    }
-}
-
-impl DroidData {
-    pub fn new(
-        id: &str,
-        name: &str,
-        friend_ids: &[&str],
-        appears_in: &[Episode],
-        secret_backstory: Option<&str>,
-        primary_function: Option<&str>,
-    ) -> DroidData {
-        DroidData {
-            id: id.to_owned(),
-            name: name.to_owned(),
-            friend_ids: friend_ids
-                .to_owned()
-                .into_iter()
-                .map(|f| f.to_owned())
-                .collect(),
-            appears_in: appears_in.to_vec(),
-            secret_backstory: secret_backstory.map(|b| b.to_owned()),
-            primary_function: primary_function.map(|p| p.to_owned()),
-        }
-    }
-}
-
-impl Database {
-    pub fn new() -> Database {
-        let mut humans = HashMap::new();
-        let mut droids = HashMap::new();
-
-        humans.insert(
-            "1000".to_owned(),
-            HumanData::new(
-                "1000",
-                "Luke Skywalker",
-                &["1002", "1003", "2000", "2001"],
-                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
-                None,
-                Some("Tatooine"),
-            ),
-        );
-
-        humans.insert(
-            "1001".to_owned(),
-            HumanData::new(
-                "1001",
-                "Darth Vader",
-                &["1004"],
-                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
-                None,
-                Some("Tatooine"),
-            ),
-        );
-
-        humans.insert(
-            "1002".to_owned(),
-            HumanData::new(
-                "1002",
-                "Han Solo",
-                &["1000", "1003", "2001"],
-                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
-                None,
-                None,
-            ),
-        );
-
-        humans.insert(
-            "1003".to_owned(),
-            HumanData::new(
-                "1003",
-                "Leia Organa",
-                &["1000", "1002", "2000", "2001"],
-                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
-                None,
-                Some("Alderaan"),
-            ),
-        );
-
-        humans.insert(
-            "1004".to_owned(),
-            HumanData::new(
-                "1004",
-                "Wilhuff Tarkin",
-                &["1001"],
-                &[Episode::NewHope],
-                None,
-                None,
-            ),
-        );
-
-        droids.insert(
-            "2000".to_owned(),
-            DroidData::new(
-                "2000",
-                "C-3PO",
-                &["1000", "1002", "1003", "2001"],
-                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
-                None,
-                Some("Protocol"),
-            ),
-        );
-
-        droids.insert(
-            "2001".to_owned(),
-            DroidData::new(
-                "2001",
-                "R2-D2",
-                &["1000", "1002", "1003"],
-                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
-                None,
-                Some("Astromech"),
-            ),
-        );
-
-        Database { humans, droids }
-    }
-
-    pub fn get_hero(&self, episode: Option<Episode>) -> &dyn Character {
-        if episode == Some(Episode::Empire) {
-            self.get_human("1000").unwrap().as_character()
-        } else {
-            self.get_droid("2001").unwrap().as_character()
-        }
-    }
-
-    pub fn get_human(&self, id: &str) -> Option<&dyn Human> {
-        self.humans.get(id).map(|h| h as &dyn Human)
-    }
-
-    pub fn get_droid(&self, id: &str) -> Option<&dyn Droid> {
-        self.droids.get(id).map(|d| d as &dyn Droid)
-    }
-
-    pub fn get_character(&self, id: &str) -> Option<&dyn Character> {
-        if let Some(h) = self.humans.get(id) {
-            Some(h)
-        } else if let Some(d) = self.droids.get(id) {
-            Some(d)
-        } else {
-            None
-        }
-    }
-
-    pub fn get_friends(&self, c: &dyn Character) -> Vec<&dyn Character> {
-        c.friend_ids()
-            .iter()
-            .flat_map(|id| self.get_character(id))
-            .collect()
-    }
-}
diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs
index e0818e57b..8693f4b2c 100644
--- a/juniper/src/tests/fixtures/starwars/schema.rs
+++ b/juniper/src/tests/fixtures/starwars/schema.rs
@@ -1,164 +1,385 @@
 #![allow(missing_docs)]
 
+use std::{collections::HashMap, pin::Pin};
+
 use crate::{
-    executor::Context,
-    graphql_subscription,
-    tests::fixtures::starwars::model::{Character, Database, Droid, Episode, Human},
-    GraphQLObject,
+    graphql_interface, graphql_object, graphql_subscription, Context, DefaultScalarValue,
+    GraphQLEnum,
 };
-use std::pin::Pin;
-
-impl Context for Database {}
 
-graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| {
-    description: "A character in the Star Wars Trilogy"
+pub struct Query;
 
-    field id() -> &str as "The id of the character" {
-        self.id()
+#[graphql_object(context = Database, scalar = DefaultScalarValue)]
+/// The root query object of the schema
+impl Query {
+    #[graphql(arguments(id(description = "id of the human")))]
+    fn human(database: &Database, id: String) -> Option<&Human> {
+        database.get_human(&id)
     }
 
-    field name() -> Option<&str> as "The name of the character" {
-        Some(self.name())
+    #[graphql(arguments(id(description = "id of the droid")))]
+    fn droid(database: &Database, id: String) -> Option<&Droid> {
+        database.get_droid(&id)
     }
 
-    field friends(&executor) -> Vec<&dyn Character>
-    as "The friends of the character" {
-        executor.context().get_friends(self.as_character())
+    #[graphql(arguments(episode(
+        description = "If omitted, returns the hero of the whole saga. \
+                       If provided, returns the hero of that particular episode"
+    )))]
+    fn hero(database: &Database, episode: Option<Episode>) -> Option<CharacterValue> {
+        Some(database.get_hero(episode))
     }
+}
 
-    field appears_in() -> &[Episode] as "Which movies they appear in" {
-        self.appears_in()
+pub struct Subscription;
+
+type HumanStream = Pin<Box<dyn futures::Stream<Item = Human> + Send>>;
+
+#[graphql_subscription(context = Database)]
+/// Super basic subscription fixture
+impl Subscription {
+    async fn async_human(context: &Database) -> HumanStream {
+        let human = context.get_human("1000").unwrap().clone();
+        Box::pin(futures::stream::once(futures::future::ready(human)))
     }
+}
+#[derive(GraphQLEnum, Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Episode {
+    #[graphql(name = "NEW_HOPE")]
+    NewHope,
+    Empire,
+    Jedi,
+}
+
+#[graphql_interface(for = [Human, Droid], context = Database, scalar = DefaultScalarValue)]
+/// A character in the Star Wars Trilogy
+pub trait Character {
+    /// The id of the character
+    fn id(&self) -> &str;
+
+    /// The name of the character
+    fn name(&self) -> Option<&str>;
+
+    /// The friends of the character
+    fn friends(&self, ctx: &Database) -> Vec<CharacterValue>;
+
+    /// Which movies they appear in
+    fn appears_in(&self) -> &[Episode];
 
-    instance_resolvers: |&context| {
-        &dyn Human => context.get_human(&self.id()),
-        &dyn Droid => context.get_droid(&self.id()),
+    #[graphql_interface(ignore)]
+    fn friends_ids(&self) -> &[String];
+}
+
+#[derive(Clone)]
+pub struct Human {
+    id: String,
+    name: String,
+    friend_ids: Vec<String>,
+    appears_in: Vec<Episode>,
+    secret_backstory: Option<String>,
+    home_planet: Option<String>,
+}
+
+impl Human {
+    pub fn new(
+        id: &str,
+        name: &str,
+        friend_ids: &[&str],
+        appears_in: &[Episode],
+        secret_backstory: Option<&str>,
+        home_planet: Option<&str>,
+    ) -> Self {
+        Self {
+            id: id.to_owned(),
+            name: name.to_owned(),
+            friend_ids: friend_ids
+                .to_owned()
+                .into_iter()
+                .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()),
+        }
     }
-});
+}
 
-#[crate::graphql_object(
-    Context = Database,
-    Scalar = crate::DefaultScalarValue,
-    interfaces = [&dyn Character],
-    // FIXME: make async work
-    noasync
-)]
 /// A humanoid creature in the Star Wars universe.
-impl<'a> &'a dyn Human {
+#[graphql_object(
+    context = Database,
+    scalar = DefaultScalarValue,
+    interfaces = CharacterValue,
+)]
+impl Human {
     /// The id of the human
     fn id(&self) -> &str {
-        self.id()
+        &self.id
     }
 
     /// The name of the human
     fn name(&self) -> Option<&str> {
-        Some(self.name())
+        Some(self.name.as_str())
     }
 
     /// The friends of the human
-    fn friends(&self, ctx: &Database) -> Vec<&dyn Character> {
-        ctx.get_friends(self.as_character())
+    fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
+        ctx.get_friends(self)
     }
 
     /// Which movies they appear in
     fn appears_in(&self) -> &[Episode] {
-        self.appears_in()
+        &self.appears_in
     }
 
     /// The home planet of the human
     fn home_planet(&self) -> &Option<String> {
-        self.home_planet()
+        &self.home_planet
+    }
+}
+
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl Character for Human {
+    fn id(&self) -> &str {
+        &self.id
+    }
+
+    fn name(&self) -> Option<&str> {
+        Some(&self.name)
+    }
+
+    fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
+        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,
+    name: String,
+    friend_ids: Vec<String>,
+    appears_in: Vec<Episode>,
+    secret_backstory: Option<String>,
+    primary_function: Option<String>,
+}
+
+impl Droid {
+    pub fn new(
+        id: &str,
+        name: &str,
+        friend_ids: &[&str],
+        appears_in: &[Episode],
+        secret_backstory: Option<&str>,
+        primary_function: Option<&str>,
+    ) -> Self {
+        Self {
+            id: id.to_owned(),
+            name: name.to_owned(),
+            friend_ids: friend_ids
+                .to_owned()
+                .into_iter()
+                .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),
+        }
     }
 }
 
-#[crate::graphql_object(
-    Context = Database,
-    Scalar = crate::DefaultScalarValue,
-    interfaces = [&dyn Character],
-    // FIXME: make async work
-    noasync
-)]
 /// A mechanical creature in the Star Wars universe.
-impl<'a> &'a dyn Droid {
+#[graphql_object(
+    context = Database,
+    scalar = DefaultScalarValue,
+    interfaces = CharacterValue,
+)]
+impl Droid {
     /// The id of the droid
     fn id(&self) -> &str {
-        self.id()
+        &self.id
     }
 
     /// The name of the droid
     fn name(&self) -> Option<&str> {
-        Some(self.name())
+        Some(self.name.as_str())
     }
 
     /// The friends of the droid
-    fn friends(&self, ctx: &Database) -> Vec<&dyn Character> {
-        ctx.get_friends(self.as_character())
+    fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
+        ctx.get_friends(self)
     }
 
     /// Which movies they appear in
     fn appears_in(&self) -> &[Episode] {
-        self.appears_in()
+        &self.appears_in
     }
 
     /// The primary function of the droid
     fn primary_function(&self) -> &Option<String> {
-        self.primary_function()
+        &self.primary_function
     }
 }
 
-pub struct Query;
+#[graphql_interface(scalar = DefaultScalarValue)]
+impl Character for Droid {
+    fn id(&self) -> &str {
+        &self.id
+    }
 
-#[crate::graphql_object(
-    Context = Database,
-    Scalar = crate::DefaultScalarValue,
-    // FIXME: make async work
-    noasync
-)]
-/// The root query object of the schema
-impl Query {
-    #[graphql(arguments(id(description = "id of the human")))]
-    fn human(database: &Database, id: String) -> Option<&dyn Human> {
-        database.get_human(&id)
+    fn name(&self) -> Option<&str> {
+        Some(&self.name)
     }
 
-    #[graphql(arguments(id(description = "id of the droid")))]
-    fn droid(database: &Database, id: String) -> Option<&dyn Droid> {
-        database.get_droid(&id)
+    fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
+        ctx.get_friends(self)
     }
 
-    #[graphql(arguments(episode(
-        description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode"
-    )))]
-    fn hero(database: &Database, episode: Option<Episode>) -> Option<&dyn Character> {
-        Some(database.get_hero(episode).as_character())
+    fn appears_in(&self) -> &[Episode] {
+        &self.appears_in
+    }
+
+    fn friends_ids(&self) -> &[String] {
+        &self.friend_ids
     }
 }
 
-#[derive(GraphQLObject)]
-#[graphql(description = "A humanoid creature in the Star Wars universe")]
-#[derive(Clone)]
-/// A humanoid creature in the Star Wars universe.
-/// TODO: remove this when async interfaces are merged
-struct HumanSubscription {
-    id: String,
-    name: String,
-    home_planet: String,
+#[derive(Default, Clone)]
+pub struct Database {
+    humans: HashMap<String, Human>,
+    droids: HashMap<String, Droid>,
 }
 
-pub struct Subscription;
+impl Context for Database {}
 
-type HumanStream = Pin<Box<dyn futures::Stream<Item = HumanSubscription> + Send>>;
+impl Database {
+    pub fn new() -> Database {
+        let mut humans = HashMap::new();
+        let mut droids = HashMap::new();
 
-#[graphql_subscription(context = Database)]
-/// Super basic subscription fixture
-impl Subscription {
-    async fn async_human() -> HumanStream {
-        Box::pin(futures::stream::once(async {
-            HumanSubscription {
-                id: "stream id".to_string(),
-                name: "stream name".to_string(),
-                home_planet: "stream home planet".to_string(),
-            }
-        }))
+        humans.insert(
+            "1000".to_owned(),
+            Human::new(
+                "1000",
+                "Luke Skywalker",
+                &["1002", "1003", "2000", "2001"],
+                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
+                None,
+                Some("Tatooine"),
+            ),
+        );
+
+        humans.insert(
+            "1001".to_owned(),
+            Human::new(
+                "1001",
+                "Darth Vader",
+                &["1004"],
+                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
+                None,
+                Some("Tatooine"),
+            ),
+        );
+
+        humans.insert(
+            "1002".to_owned(),
+            Human::new(
+                "1002",
+                "Han Solo",
+                &["1000", "1003", "2001"],
+                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
+                None,
+                None,
+            ),
+        );
+
+        humans.insert(
+            "1003".to_owned(),
+            Human::new(
+                "1003",
+                "Leia Organa",
+                &["1000", "1002", "2000", "2001"],
+                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
+                None,
+                Some("Alderaan"),
+            ),
+        );
+
+        humans.insert(
+            "1004".to_owned(),
+            Human::new(
+                "1004",
+                "Wilhuff Tarkin",
+                &["1001"],
+                &[Episode::NewHope],
+                None,
+                None,
+            ),
+        );
+
+        droids.insert(
+            "2000".to_owned(),
+            Droid::new(
+                "2000",
+                "C-3PO",
+                &["1000", "1002", "1003", "2001"],
+                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
+                None,
+                Some("Protocol"),
+            ),
+        );
+
+        droids.insert(
+            "2001".to_owned(),
+            Droid::new(
+                "2001",
+                "R2-D2",
+                &["1000", "1002", "1003"],
+                &[Episode::NewHope, Episode::Empire, Episode::Jedi],
+                None,
+                Some("Astromech"),
+            ),
+        );
+
+        Database { humans, droids }
+    }
+
+    pub fn get_hero(&self, episode: Option<Episode>) -> CharacterValue {
+        if episode == Some(Episode::Empire) {
+            self.get_human("1000").unwrap().clone().into()
+        } else {
+            self.get_droid("2001").unwrap().clone().into()
+        }
+    }
+
+    pub fn get_human(&self, id: &str) -> Option<&Human> {
+        self.humans.get(id)
+    }
+
+    pub fn get_droid(&self, id: &str) -> Option<&Droid> {
+        self.droids.get(id)
+    }
+
+    pub fn get_character(&self, id: &str) -> Option<CharacterValue> {
+        if let Some(h) = self.humans.get(id) {
+            Some(h.clone().into())
+        } else if let Some(d) = self.droids.get(id) {
+            Some(d.clone().into())
+        } else {
+            None
+        }
+    }
+
+    pub fn get_friends(&self, c: &dyn Character) -> Vec<CharacterValue> {
+        c.friends_ids()
+            .iter()
+            .flat_map(|id| self.get_character(id))
+            .collect()
     }
 }
diff --git a/juniper/src/tests/fixtures/starwars/schema_language.rs b/juniper/src/tests/fixtures/starwars/schema_language.rs
index c2f16023c..7a71c8d7a 100644
--- a/juniper/src/tests/fixtures/starwars/schema_language.rs
+++ b/juniper/src/tests/fixtures/starwars/schema_language.rs
@@ -8,7 +8,8 @@ mod tests {
     use crate::{
         schema::model::RootNode,
         tests::fixtures::starwars::{
-            model::Database, schema::Query, schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION,
+            schema::{Database, Query},
+            schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION,
         },
         types::scalars::{EmptyMutation, EmptySubscription},
     };
diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs
index 46b7a21c2..38259eead 100644
--- a/juniper/src/tests/introspection_tests.rs
+++ b/juniper/src/tests/introspection_tests.rs
@@ -1,14 +1,15 @@
 use std::collections::HashSet;
 
-use super::schema_introspection::*;
 use crate::{
     executor::Variables,
     introspection::IntrospectionFormat,
     schema::model::RootNode,
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     types::scalars::{EmptyMutation, EmptySubscription},
 };
 
+use super::schema_introspection::*;
+
 #[tokio::test]
 async fn test_introspection_query_type_name() {
     let doc = r#"
diff --git a/juniper/src/tests/query_tests.rs b/juniper/src/tests/query_tests.rs
index c68e3a529..d671c5bf6 100644
--- a/juniper/src/tests/query_tests.rs
+++ b/juniper/src/tests/query_tests.rs
@@ -2,7 +2,7 @@ use crate::{
     ast::InputValue,
     executor::Variables,
     schema::model::RootNode,
-    tests::fixtures::starwars::{model::Database, schema::Query},
+    tests::fixtures::starwars::schema::{Database, Query},
     types::scalars::{EmptyMutation, EmptySubscription},
     value::Value,
 };
@@ -641,8 +641,8 @@ async fn test_query_inline_fragments_droid() {
                     "hero",
                     Value::object(
                         vec![
-                            ("name", Value::scalar("R2-D2")),
                             ("__typename", Value::scalar("Droid")),
+                            ("name", Value::scalar("R2-D2")),
                             ("primaryFunction", Value::scalar("Astromech")),
                         ]
                         .into_iter()
@@ -662,8 +662,8 @@ async fn test_query_inline_fragments_human() {
     let doc = r#"
         query InlineFragments {
             hero(episode: EMPIRE) {
-                name
                 __typename
+                name
             }
         }
         "#;
@@ -682,8 +682,8 @@ async fn test_query_inline_fragments_human() {
                     "hero",
                     Value::object(
                         vec![
-                            ("name", Value::scalar("Luke Skywalker")),
                             ("__typename", Value::scalar("Human")),
+                            ("name", Value::scalar("Luke Skywalker")),
                         ]
                         .into_iter()
                         .collect(),