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

Issue:
Allow argument documentation 
**Is your feature request related to a problem? Please describe.**

I like to provide clear documentation to my end users about everything in the schema, and this includes arguments.

**Describe the solution you'd like**

As of Rust 1.39, attributes are allowed on parameters. rust-lang/rfcs#2565.

I would like to see these used to provide documentation for arguments.

```rust
#[juniper::object(Context = Context)]
impl QueryRoot {
    #[doc = "Ability to document the endpoint already exists"]
    fn some_endpoint(
        context: &Context,
        #[doc = "Ability to document arguments would be awesome"]
        id: ID,
        #[doc = "Only include items in one of these states. Defaults to all states."]
        states: Option<Vec<StateInput>>
    ) -> SomeObject {
        todo!()
    }
}
```

**Describe alternatives you've considered**

We don't need to use `#[doc = ""]`. We could also use a custom attribute.

**Additional context**

Documenting arguments is common. For example, on the [GitHub v4 API](https://developer.github.com/v4/query/), everything considered a "Connection" has its arguments clearly documented.

#334 seems to suggest that this works. However, I can't seem to get it to work. Possibly it doesn't work in `#[juniper::object]` macros?


Discussion / hints:
I believe this will be enabled by https://github.com/graphql-rust/juniper/pull/441, right @davidpdrsn ?
Not sure. I will add it as a todo to look into.
Using attributes to document parametrs would be great! However, it should not hold back #441. If I am not mistaken, it is a backwards-compatible feature, so it can be added in later without breaking any of the code that might already be relying on #441. @davidpdrsn you are the best parson to decide on that.

Fix patch:
diff --git a/docs/book/content/advanced/implicit_and_explicit_null.md b/docs/book/content/advanced/implicit_and_explicit_null.md
index 014249a19..746cb7ee3 100644
--- a/docs/book/content/advanced/implicit_and_explicit_null.md
+++ b/docs/book/content/advanced/implicit_and_explicit_null.md
@@ -99,10 +99,11 @@ impl Into<UserPatch> for UserPatchInput {
 struct Context {
     session: Session,
 }
+impl juniper::Context for Context {}
 
 struct Mutation;
 
-#[juniper::graphql_object(Context=Context)]
+#[juniper::graphql_object(context = Context)]
 impl Mutation {
     fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> {
         ctx.session.patch_user(patch.into())?;
diff --git a/docs/book/content/advanced/subscriptions.md b/docs/book/content/advanced/subscriptions.md
index 95b26513a..05e01d64b 100644
--- a/docs/book/content/advanced/subscriptions.md
+++ b/docs/book/content/advanced/subscriptions.md
@@ -25,10 +25,6 @@ This example shows a subscription operation that returns two events, the strings
 sequentially: 
 
 ```rust
-# extern crate futures;
-# extern crate juniper;
-# extern crate juniper_subscriptions;
-# extern crate tokio;
 # use juniper::{graphql_object, graphql_subscription, FieldError};
 # use futures::Stream;
 # use std::pin::Pin;
@@ -40,7 +36,7 @@ sequentially:
 # pub struct Query;
 # #[graphql_object(context = Database)]
 # impl Query {
-#    fn hello_world() -> &str {
+#    fn hello_world() -> &'static str {
 #        "Hello World!"
 #    }
 # }
@@ -110,7 +106,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
 # 
 # #[graphql_object(context = Database)]
 # impl Query {
-#     fn hello_world() -> &str {
+#     fn hello_world() -> &'static str {
 #         "Hello World!"
 #     }
 # }
diff --git a/docs/book/content/quickstart.md b/docs/book/content/quickstart.md
index 7af0bb02e..1fbcd882a 100644
--- a/docs/book/content/quickstart.md
+++ b/docs/book/content/quickstart.md
@@ -6,11 +6,9 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch
 
 ## Installation
 
-!FILENAME Cargo.toml
-
 ```toml
 [dependencies]
-juniper = { git = "https://github.com/graphql-rust/juniper" }
+juniper = "0.15"
 ```
 
 ## Schema example
@@ -89,7 +87,7 @@ struct Query;
     context = Context,
 )]
 impl Query {
-    fn apiVersion() -> &str {
+    fn apiVersion() -> &'static str {
         "1.0"
     }
 
@@ -114,14 +112,13 @@ struct Mutation;
 
 #[graphql_object(
     context = Context,
-
     // If we need to use `ScalarValue` parametrization explicitly somewhere
-    // in the object definition (like here in `FieldResult`), we should
+    // in the object definition (like here in `FieldResult`), we could
     // declare an explicit type parameter for that, and specify it.
-    scalar = S,
+    scalar = S: ScalarValue + Display,
 )]
-impl<S: ScalarValue + Display> Mutation {
-    fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
+impl Mutation {
+    fn createHuman<S: ScalarValue + Display>(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
         let db = context.pool.get_connection().map_err(|e| e.map_scalar_value())?;
         let human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?;
         Ok(human)
diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md
index b5e81e5ca..414d2f299 100644
--- a/docs/book/content/types/interfaces.md
+++ b/docs/book/content/types/interfaces.md
@@ -232,6 +232,21 @@ trait Character {
 # fn main() {}
 ```
 
+Renaming policies for all [GraphQL interface][1] fields and arguments are supported as well:
+```rust
+# #![allow(deprecated)]
+# extern crate juniper;
+use juniper::graphql_interface;
+
+#[graphql_interface(rename_all = "none")] // disables any renaming
+trait Character {
+    // Now exposed as `my_id` and `my_num` in the schema
+    fn my_id(&self, my_num: i32) -> &str;
+}
+#
+# fn main() {}
+```
+
 
 ### Custom context
 
diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md
index 75867184c..86d310eca 100644
--- a/docs/book/content/types/objects/complex_fields.md
+++ b/docs/book/content/types/objects/complex_fields.md
@@ -3,9 +3,10 @@
 If you've got a struct that can't be mapped directly to GraphQL, that contains
 computed fields or circular structures, you have to use a more powerful tool:
 the `#[graphql_object]` procedural macro. This macro lets you define GraphQL object
-fields in a Rust `impl` block for a type. Note that only GraphQL fields
-can be specified in this `impl` block. If you want to define normal methods on the struct,
-you have to do so in a separate, normal `impl` block. Continuing with the
+fields in a Rust `impl` block for a type. Note, that GraphQL fields are defined in 
+this `impl` block by default. If you want to define normal methods on the struct,
+you have to do so either in a separate "normal" `impl` block, or mark them with
+`#[graphql(ignore)]` attribute to be omitted by the macro. Continuing with the
 example from the last chapter, this is how you would define `Person` using the
 macro:
 
@@ -28,12 +29,15 @@ impl Person {
     fn age(&self) -> i32 {
         self.age
     }
+
+    #[graphql(ignore)]
+    pub fn hidden_from_graphql(&self) {
+        // [...]
+    }
 }
 
-// Note that this syntax generates an implementation of the GraphQLType trait,
-// the base impl of your struct can still be written like usual:
 impl Person {
-    pub fn hidden_from_graphql(&self) {
+    pub fn hidden_from_graphql2(&self) {
         // [...]
     }
 }
@@ -44,7 +48,6 @@ impl Person {
 While this is a bit more verbose, it lets you write any kind of function in the
 field resolver. With this syntax, fields can also take arguments:
 
-
 ```rust
 # extern crate juniper;
 # use juniper::{graphql_object, GraphQLObject};
@@ -61,7 +64,7 @@ struct House {
 
 #[graphql_object]
 impl House {
-    // Creates the field inhabitantWithName(name), returning a nullable person
+    // Creates the field `inhabitantWithName(name)`, returning a nullable `Person`.
     fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
         self.inhabitants.iter().find(|p| p.name == name)
     }
@@ -127,15 +130,29 @@ impl Person {
 # fn main() { }
 ```
 
+Or provide a different renaming policy on a `impl` block for all its fields:
+```rust
+# extern crate juniper;
+# use juniper::graphql_object;
+struct Person;
+
+#[graphql_object(rename_all = "none")] // disables any renaming
+impl Person {
+    // Now exposed as `renamed_field` in the schema
+    fn renamed_field() -> bool {
+        true
+    }
+}
+#
+# fn main() {}
+```
+
 ## Customizing arguments
 
 Method field arguments can also be customized.
 
 They can have custom descriptions and default values.
 
-**Note**: The syntax for this is currently a little awkward. 
-This will become better once the [Rust RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented.
-
 ```rust
 # extern crate juniper;
 # use juniper::graphql_object;
@@ -144,21 +161,22 @@ struct Person {}
 
 #[graphql_object]
 impl Person {
-    #[graphql(
-        arguments(
-            arg1(
-                // Set a default value which will be injected if not present.
-                // The default can be any valid Rust expression, including a function call, etc.
-                default = true,
-                // Set a description.
-                description = "The first argument..."
-            ),
-            arg2(
-                default = 0,
-            )
-        )
-    )]
-    fn field1(&self, arg1: bool, arg2: i32) -> String {
+    fn field1(
+        &self,
+        #[graphql(
+            // Arguments can also be renamed if required.
+            name = "arg",
+            // Set a default value which will be injected if not present.
+            // The default can be any valid Rust expression, including a function call, etc.
+            default = true,
+            // Set a description.
+            description = "The first argument..."
+        )]
+        arg1: bool,
+        // If default expression is not specified then `Default::default()` value is used.
+        #[graphql(default)]
+        arg2: i32,
+    ) -> String {
         format!("{} {}", arg1, arg2)
     }
 }
@@ -166,13 +184,23 @@ impl Person {
 # fn main() { }
 ```
 
-## More features
+Provide a different renaming policy on a `impl` block also implies for arguments:
+```rust
+# extern crate juniper;
+# use juniper::graphql_object;
+struct Person;
 
-GraphQL fields expose more features than Rust's standard method syntax gives us:
+#[graphql_object(rename_all = "none")] // disables any renaming
+impl Person {
+    // Now exposed as `my_arg` in the schema
+    fn field(my_arg: bool) -> bool {
+        my_arg
+    }
+}
+#
+# fn main() {}
+```
 
-* Per-field description and deprecation messages
-* Per-argument default values
-* Per-argument descriptions
+## More features
 
-These, and more features, are described more thoroughly in [the reference
-documentation](https://docs.rs/juniper/latest/juniper/macro.object.html).
+These, and more features, are described more thoroughly in [the reference documentation](https://docs.rs/juniper/latest/juniper/attr.graphql_object.html).
diff --git a/docs/book/content/types/objects/defining_objects.md b/docs/book/content/types/objects/defining_objects.md
index 1a4dcb7da..5f32c6862 100644
--- a/docs/book/content/types/objects/defining_objects.md
+++ b/docs/book/content/types/objects/defining_objects.md
@@ -152,7 +152,22 @@ struct Person {
     name: String,
     age: i32,
     #[graphql(name = "websiteURL")]
-    website_url: Option<String>, // Now exposed as websiteURL in the schema
+    website_url: Option<String>, // now exposed as `websiteURL` in the schema
+}
+#
+# fn main() {}
+```
+
+Or provide a different renaming policy on a struct for all its fields:
+```rust
+# extern crate juniper;
+# use juniper::GraphQLObject;
+#[derive(GraphQLObject)]
+#[graphql(rename_all = "none")] // disables any renaming
+struct Person {
+    name: String,
+    age: i32,
+    website_url: Option<String>, // now exposed as `website_url` in the schema
 }
 #
 # fn main() {}
@@ -181,9 +196,9 @@ The `name`, `description`, and `deprecation` arguments can of course be
 combined. Some restrictions from the GraphQL spec still applies though; you can
 only deprecate object fields and enum values.
 
-## Skipping fields
+## Ignoring fields
 
-By default all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(skip)]`:
+By default, all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(ignore)]`:
 
 ```rust
 # extern crate juniper;
@@ -192,9 +207,9 @@ By default all fields in a `GraphQLObject` are included in the generated GraphQL
 struct Person {
     name: String,
     age: i32,
-    #[graphql(skip)]
+    #[graphql(ignore)]
     # #[allow(dead_code)]
-    password_hash: String, // This cannot be queried or modified from GraphQL
+    password_hash: String, // cannot be queried or modified from GraphQL
 }
 #
 # fn main() {}
diff --git a/docs/book/content/types/objects/error_handling.md b/docs/book/content/types/objects/error_handling.md
index ea64e7642..4adc269fa 100644
--- a/docs/book/content/types/objects/error_handling.md
+++ b/docs/book/content/types/objects/error_handling.md
@@ -17,8 +17,7 @@ it will bubble up to the surrounding framework and hopefully be dealt with
 there.
 
 For recoverable errors, Juniper works well with the built-in `Result` type, you
-can use the `?` operator or the `try!` macro and things will generally just work
-as you expect them to:
+can use the `?` operator and things will generally just work as you expect them to:
 
 ```rust
 # extern crate juniper;
@@ -36,7 +35,7 @@ struct Example {
 
 #[graphql_object]
 impl Example {
-    fn contents() -> FieldResult<String> {
+    fn contents(&self) -> FieldResult<String> {
         let mut file = File::open(&self.filename)?;
         let mut contents = String::new();
         file.read_to_string(&mut contents)?;
@@ -44,13 +43,10 @@ impl Example {
     }
 
     fn foo() -> FieldResult<Option<String>> {
-      // Some invalid bytes.
-      let invalid = vec![128, 223];
+        // Some invalid bytes.
+        let invalid = vec![128, 223];
 
-      match str::from_utf8(&invalid) {
-        Ok(s) => Ok(Some(s.to_string())),
-        Err(e) => Err(e)?,
-      }
+        Ok(Some(str::from_utf8(&invalid)?.to_string()))
     }
 }
 #
@@ -66,7 +62,6 @@ there - those errors are automatically converted into `FieldError`.
 
 Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability).
 
-
 When a field returns an error, the field's result is replaced by `null`, an
 additional `errors` object is created at the top level of the response, and the
 execution is resumed. For example, with the previous example and the following
@@ -86,12 +81,12 @@ returned:
 
 !FILENAME Response for nullable field with error
 
-```js
+```json
 {
   "data": {
     "example": {
       contents: "<Contents of the file>",
-      foo: null,
+      foo: null
     }
   },
   "errors": [
@@ -120,7 +115,7 @@ following would be returned:
 
 !FILENAME Response for non-null field with error and no nullable parent
 
-```js
+```json
 {
   "errors": [
     "message": "Permission denied (os error 13)",
@@ -162,11 +157,11 @@ struct Example {
 
 #[graphql_object]
 impl Example {
-    fn whatever() -> Result<bool, CustomError> {
-      if let Some(value) = self.whatever {
-        return Ok(value);
-      }
-      Err(CustomError::WhateverNotSet)
+    fn whatever(&self) -> Result<bool, CustomError> {
+        if let Some(value) = self.whatever {
+            return Ok(value);
+        }
+        Err(CustomError::WhateverNotSet)
     }
 }
 #
diff --git a/docs/book/content/types/objects/using_contexts.md b/docs/book/content/types/objects/using_contexts.md
index 0576a6644..10164e466 100644
--- a/docs/book/content/types/objects/using_contexts.md
+++ b/docs/book/content/types/objects/using_contexts.md
@@ -63,8 +63,8 @@ impl User {
     //    with the context type.
     // Note: 
     //   - the type must be a reference
-    //   - the name of the argument SHOULD be context
-    fn friends(&self, context: &Database) -> Vec<&User> {
+    //   - the name of the argument SHOULD be `context`
+    fn friends<'db>(&self, context: &'db Database) -> Vec<&'db User> {
 
         // 5. Use the database to lookup users
         self.friend_ids.iter()
diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs
index 6464a71eb..c5021ae6a 100644
--- a/examples/actix_subscriptions/src/main.rs
+++ b/examples/actix_subscriptions/src/main.rs
@@ -11,7 +11,7 @@ use actix_web::{
 use juniper::{
     graphql_object, graphql_subscription,
     tests::fixtures::starwars::schema::{Character as _, Database, Query},
-    DefaultScalarValue, EmptyMutation, FieldError, RootNode,
+    DefaultScalarValue, EmptyMutation, FieldError, RootNode, Value,
 };
 use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
 use juniper_graphql_ws::ConnectionConfig;
diff --git a/examples/basic_subscriptions/Cargo.toml b/examples/basic_subscriptions/Cargo.toml
index ccb294db8..8367cd167 100644
--- a/examples/basic_subscriptions/Cargo.toml
+++ b/examples/basic_subscriptions/Cargo.toml
@@ -5,8 +5,6 @@ edition = "2018"
 publish = false
 authors = ["Jordao Rosario <jordao.rosario01@gmail.com>"]
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
 futures = "0.3"
 serde = { version = "1.0", features = ["derive"] }
diff --git a/examples/basic_subscriptions/src/main.rs b/examples/basic_subscriptions/src/main.rs
index a8e3736c3..82d4b46fb 100644
--- a/examples/basic_subscriptions/src/main.rs
+++ b/examples/basic_subscriptions/src/main.rs
@@ -24,7 +24,7 @@ pub struct Query;
 
 #[graphql_object(context = Database)]
 impl Query {
-    fn hello_world() -> &str {
+    fn hello_world() -> &'static str {
         "Hello World!"
     }
 }
diff --git a/examples/warp_subscriptions/Cargo.toml b/examples/warp_subscriptions/Cargo.toml
index 5e0e76cfe..4c0b0025d 100644
--- a/examples/warp_subscriptions/Cargo.toml
+++ b/examples/warp_subscriptions/Cargo.toml
@@ -13,6 +13,7 @@ serde_json = "1.0"
 tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
 warp = "0.3"
 async-stream = "0.3"
+
 juniper = { path = "../../juniper" }
 juniper_graphql_ws = { path = "../../juniper_graphql_ws" }
 juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] }
diff --git a/examples/warp_subscriptions/src/main.rs b/examples/warp_subscriptions/src/main.rs
index 21e2668bb..c7a6f855a 100644
--- a/examples/warp_subscriptions/src/main.rs
+++ b/examples/warp_subscriptions/src/main.rs
@@ -5,7 +5,7 @@ use std::{env, pin::Pin, sync::Arc, time::Duration};
 use futures::{FutureExt as _, Stream};
 use juniper::{
     graphql_object, graphql_subscription, DefaultScalarValue, EmptyMutation, FieldError,
-    GraphQLEnum, RootNode,
+    GraphQLEnum, RootNode, Value,
 };
 use juniper_graphql_ws::ConnectionConfig;
 use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws};
diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md
index dcbc294d2..eb0829b90 100644
--- a/juniper/CHANGELOG.md
+++ b/juniper/CHANGELOG.md
@@ -1,8 +1,19 @@
 # master
 
-- Allow spreading interface fragments on unions and other interfaces ([#965](https://github.com/graphql-rust/juniper/pull/965), [#798](https://github.com/graphql-rust/juniper/issues/798))
-- Expose `GraphQLRequest` fields ([#750](https://github.com/graphql-rust/juniper/issues/750))
-- Support using Rust array as GraphQL list ([#966](https://github.com/graphql-rust/juniper/pull/966), [#918](https://github.com/graphql-rust/juniper/issues/918))
+## Breaking Changes
+
+- `#[graphql_object]` and `#[graphql_subscription]` macros expansion now preserves defined `impl` blocks "as is" and reuses defined methods in opaque way. ([#971](https://github.com/graphql-rust/juniper/pull/971)
+- `rename = "<policy>"` attribute's argument renamed to `rename_all = "<policy>"`. ([#971](https://github.com/graphql-rust/juniper/pull/971)
+
+## Features
+
+- Support using Rust array as GraphQL list. ([#966](https://github.com/graphql-rust/juniper/pull/966), [#918](https://github.com/graphql-rust/juniper/issues/918))
+- Expose `GraphQLRequest` fields. ([#750](https://github.com/graphql-rust/juniper/issues/750))
+- `#[graphql_interface]` macro now supports `rename_all = "<policy>"` argument influencing its fields and their arguments. ([#971](https://github.com/graphql-rust/juniper/pull/971)
+
+## Fixes
+
+- Allow spreading interface fragments on unions and other interfaces. ([#965](https://github.com/graphql-rust/juniper/pull/965), [#798](https://github.com/graphql-rust/juniper/issues/798))
 
 # [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7)
 
@@ -41,8 +52,8 @@
 ## Features
 
 - Added async support. ([#2](https://github.com/graphql-rust/juniper/issues/2))
-    - `execute()` is now async. Synchronous execution can still be used via `execute_sync()`.
-    - Field resolvers may optionally be declared as `async` and return a future.
+  - `execute()` is now async. Synchronous execution can still be used via `execute_sync()`.
+  - Field resolvers may optionally be declared as `async` and return a future.
 
 - Added *experimental* support for GraphQL subscriptions. ([#433](https://github.com/graphql-rust/juniper/pull/433))
 
diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs
index 07aed950b..06ac627d3 100644
--- a/juniper/src/ast.rs
+++ b/juniper/src/ast.rs
@@ -160,12 +160,13 @@ pub trait FromInputValue<S = DefaultScalarValue>: Sized {
     /// Performs the conversion.
     fn from_input_value(v: &InputValue<S>) -> Option<Self>;
 
-    /// Performs the conversion from an absent value (e.g. to distinguish between implicit and
-    /// explicit null). The default implementation just uses `from_input_value` as if an explicit
-    /// null were provided. This conversion must not fail.
-    fn from_implicit_null() -> Self {
+    /// Performs the conversion from an absent value (e.g. to distinguish between
+    /// implicit and explicit null). The default implementation just uses
+    /// `from_input_value` as if an explicit null were provided.
+    ///
+    /// This conversion must not fail.
+    fn from_implicit_null() -> Option<Self> {
         Self::from_input_value(&InputValue::<S>::Null)
-            .expect("input value conversion from null must not fail")
     }
 }
 
diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs
index 15d66c949..95d83382c 100644
--- a/juniper/src/integrations/bson.rs
+++ b/juniper/src/integrations/bson.rs
@@ -1,3 +1,5 @@
+//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
+
 use bson::{oid::ObjectId, DateTime as UtcDateTime};
 use chrono::prelude::*;
 
diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs
index 03be9dda7..56613fdd6 100644
--- a/juniper/src/integrations/chrono.rs
+++ b/juniper/src/integrations/chrono.rs
@@ -253,72 +253,68 @@ mod integration_test {
 
     use crate::{
         executor::Variables,
+        graphql_object, graphql_value,
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
-        value::Value,
     };
 
     #[tokio::test]
     async fn test_serialization() {
         struct Root;
 
-        #[crate::graphql_object]
+        #[graphql_object]
         #[cfg(feature = "scalar-naivetime")]
         impl Root {
-            fn exampleNaiveDate() -> NaiveDate {
+            fn example_naive_date() -> NaiveDate {
                 NaiveDate::from_ymd(2015, 3, 14)
             }
-            fn exampleNaiveDateTime() -> NaiveDateTime {
+            fn example_naive_date_time() -> NaiveDateTime {
                 NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
             }
-            fn exampleNaiveTime() -> NaiveTime {
+            fn example_naive_time() -> NaiveTime {
                 NaiveTime::from_hms(16, 7, 8)
             }
-            fn exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
+            fn example_date_time_fixed_offset() -> DateTime<FixedOffset> {
                 DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
             }
-            fn exampleDateTimeUtc() -> DateTime<Utc> {
+            fn example_date_time_utc() -> DateTime<Utc> {
                 Utc.timestamp(61, 0)
             }
         }
 
-        #[crate::graphql_object]
+        #[graphql_object]
         #[cfg(not(feature = "scalar-naivetime"))]
         impl Root {
-            fn exampleNaiveDate() -> NaiveDate {
+            fn example_naive_date() -> NaiveDate {
                 NaiveDate::from_ymd(2015, 3, 14)
             }
-            fn exampleNaiveDateTime() -> NaiveDateTime {
+            fn example_naive_date_time() -> NaiveDateTime {
                 NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
             }
-            fn exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
+            fn example_date_time_fixed_offset() -> DateTime<FixedOffset> {
                 DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
             }
-            fn exampleDateTimeUtc() -> DateTime<Utc> {
+            fn example_date_time_utc() -> DateTime<Utc> {
                 Utc.timestamp(61, 0)
             }
         }
 
         #[cfg(feature = "scalar-naivetime")]
-        let doc = r#"
-        {
+        let doc = r#"{
             exampleNaiveDate,
             exampleNaiveDateTime,
             exampleNaiveTime,
             exampleDateTimeFixedOffset,
             exampleDateTimeUtc,
-        }
-        "#;
+        }"#;
 
         #[cfg(not(feature = "scalar-naivetime"))]
-        let doc = r#"
-        {
+        let doc = r#"{
             exampleNaiveDate,
             exampleNaiveDateTime,
             exampleDateTimeFixedOffset,
             exampleDateTimeUtc,
-        }
-        "#;
+        }"#;
 
         let schema = RootNode::new(
             Root,
@@ -332,26 +328,26 @@ mod integration_test {
 
         assert_eq!(errs, []);
 
+        #[cfg(feature = "scalar-naivetime")]
+        assert_eq!(
+            result,
+            graphql_value!({
+                "exampleNaiveDate": "2015-03-14",
+                "exampleNaiveDateTime": 1_467_969_011.0,
+                "exampleNaiveTime": "16:07:08",
+                "exampleDateTimeFixedOffset": "1996-12-19T16:39:57-08:00",
+                "exampleDateTimeUtc": "1970-01-01T00:01:01+00:00",
+            }),
+        );
+        #[cfg(not(feature = "scalar-naivetime"))]
         assert_eq!(
             result,
-            Value::object(
-                vec![
-                    ("exampleNaiveDate", Value::scalar("2015-03-14")),
-                    ("exampleNaiveDateTime", Value::scalar(1_467_969_011.0)),
-                    #[cfg(feature = "scalar-naivetime")]
-                    ("exampleNaiveTime", Value::scalar("16:07:08")),
-                    (
-                        "exampleDateTimeFixedOffset",
-                        Value::scalar("1996-12-19T16:39:57-08:00"),
-                    ),
-                    (
-                        "exampleDateTimeUtc",
-                        Value::scalar("1970-01-01T00:01:01+00:00"),
-                    ),
-                ]
-                .into_iter()
-                .collect()
-            )
+            graphql_value!({
+                "exampleNaiveDate": "2015-03-14",
+                "exampleNaiveDateTime": 1_467_969_011.0,
+                "exampleDateTimeFixedOffset": "1996-12-19T16:39:57-08:00",
+                "exampleDateTimeUtc": "1970-01-01T00:01:01+00:00",
+            }),
         );
     }
 }
diff --git a/juniper/src/integrations/mod.rs b/juniper/src/integrations/mod.rs
index ad4e29e3c..76ac28ba6 100644
--- a/juniper/src/integrations/mod.rs
+++ b/juniper/src/integrations/mod.rs
@@ -1,24 +1,14 @@
 //! Provides GraphQLType implementations for some external types
 
-#[doc(hidden)]
-pub mod serde;
-
+#[cfg(feature = "bson")]
+pub mod bson;
 #[cfg(feature = "chrono")]
-/// GraphQL support for [chrono](https://github.com/chronotope/chrono) types.
 pub mod chrono;
-
 #[cfg(feature = "chrono-tz")]
-/// GraphQL support for [chrono-tz](https://github.com/chronotope/chrono-tz) types.
 pub mod chrono_tz;
-
+#[doc(hidden)]
+pub mod serde;
 #[cfg(feature = "url")]
-/// GraphQL support for [url](https://github.com/servo/rust-url) types.
 pub mod url;
-
 #[cfg(feature = "uuid")]
-/// GraphQL support for [uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) types.
 pub mod uuid;
-
-#[cfg(feature = "bson")]
-/// GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
-pub mod bson;
diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs
index 649787987..492626bd9 100644
--- a/juniper/src/integrations/url.rs
+++ b/juniper/src/integrations/url.rs
@@ -1,3 +1,5 @@
+//! GraphQL support for [url](https://github.com/servo/rust-url) types.
+
 use url::Url;
 
 use crate::{
diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs
index 5a949cf7b..1f677d65d 100644
--- a/juniper/src/integrations/uuid.rs
+++ b/juniper/src/integrations/uuid.rs
@@ -1,3 +1,5 @@
+//! GraphQL support for [uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) types.
+
 #![allow(clippy::needless_lifetimes)]
 
 use uuid::Uuid;
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index c4e9bd305..8c361bfef 100644
--- a/juniper/src/lib.rs
+++ b/juniper/src/lib.rs
@@ -172,7 +172,7 @@ pub use crate::{
     types::{
         async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync},
         base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind},
-        marker::{self, GraphQLInterface, GraphQLUnion},
+        marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion},
         nullable::Nullable,
         scalars::{EmptyMutation, EmptySubscription, ID},
         subscriptions::{
diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs
index b38958dae..241878e50 100644
--- a/juniper/src/macros/mod.rs
+++ b/juniper/src/macros/mod.rs
@@ -1,6 +1,3 @@
 //! Helper definitions for macros.
 
 pub mod helper;
-
-#[cfg(test)]
-mod tests;
diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs
index beb8e0ac7..f5ac2c0f7 100644
--- a/juniper/src/schema/meta.rs
+++ b/juniper/src/schema/meta.rs
@@ -32,10 +32,10 @@ impl DeprecationStatus {
     }
 
     /// An optional reason for the deprecation, or none if `Current`.
-    pub fn reason(&self) -> Option<&String> {
+    pub fn reason(&self) -> Option<&str> {
         match self {
             DeprecationStatus::Current => None,
-            DeprecationStatus::Deprecated(ref reason) => reason.as_ref(),
+            DeprecationStatus::Deprecated(rsn) => rsn.as_deref(),
         }
     }
 }
@@ -236,26 +236,14 @@ impl<'a, S> MetaType<'a, S> {
     /// Access the description of the type, if applicable
     ///
     /// Lists, nullable wrappers, and placeholders don't have names.
-    pub fn description(&self) -> Option<&String> {
-        match *self {
-            MetaType::Scalar(ScalarMeta {
-                ref description, ..
-            })
-            | MetaType::Object(ObjectMeta {
-                ref description, ..
-            })
-            | MetaType::Enum(EnumMeta {
-                ref description, ..
-            })
-            | MetaType::Interface(InterfaceMeta {
-                ref description, ..
-            })
-            | MetaType::Union(UnionMeta {
-                ref description, ..
-            })
-            | MetaType::InputObject(InputObjectMeta {
-                ref description, ..
-            }) => description.as_ref(),
+    pub fn description(&self) -> Option<&str> {
+        match self {
+            MetaType::Scalar(ScalarMeta { description, .. })
+            | MetaType::Object(ObjectMeta { description, .. })
+            | MetaType::Enum(EnumMeta { description, .. })
+            | MetaType::Interface(InterfaceMeta { description, .. })
+            | MetaType::Union(UnionMeta { description, .. })
+            | MetaType::InputObject(InputObjectMeta { description, .. }) => description.as_deref(),
             _ => None,
         }
     }
@@ -670,7 +658,7 @@ impl<'a, S> Field<'a, S> {
 impl<'a, S> Argument<'a, S> {
     #[doc(hidden)]
     pub fn new(name: &str, arg_type: Type<'a>) -> Self {
-        Argument {
+        Self {
             name: name.to_owned(),
             description: None,
             arg_type,
diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs
index b0516fe04..953904cb0 100644
--- a/juniper/src/schema/model.rs
+++ b/juniper/src/schema/model.rs
@@ -640,12 +640,8 @@ mod test {
                 fn whatever() -> String {
                     "foo".to_string()
                 }
-                fn arr(stuff: Vec<Coordinate>) -> Option<&str> {
-                    if stuff.is_empty() {
-                        None
-                    } else {
-                        Some("stuff")
-                    }
+                fn arr(stuff: Vec<Coordinate>) -> Option<&'static str> {
+                    (!stuff.is_empty()).then(|| "stuff")
                 }
                 fn fruit() -> Fruit {
                     Fruit::Apple
diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs
index a877afe78..5651ccff5 100644
--- a/juniper/src/schema/schema.rs
+++ b/juniper/src/schema/schema.rs
@@ -1,6 +1,7 @@
 use crate::{
     ast::Selection,
     executor::{ExecutionResult, Executor, Registry},
+    graphql_object,
     types::{
         async_await::{GraphQLTypeAsync, GraphQLValueAsync},
         base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
@@ -129,18 +130,13 @@ where
     }
 }
 
-#[crate::graphql_object(
+#[graphql_object(
     name = "__Schema"
-    Context = SchemaType<'a, S>,
-    Scalar = S,
+    context = SchemaType<'a, S>,
+    scalar = S,
     internal,
-    // FIXME: make this redundant.
-    noasync,
 )]
-impl<'a, S> SchemaType<'a, S>
-where
-    S: crate::ScalarValue + 'a,
-{
+impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> {
     fn types(&self) -> Vec<TypeType<S>> {
         self.type_list()
             .into_iter()
@@ -152,18 +148,21 @@ where
                     })
                     .unwrap_or(false)
             })
-            .collect::<Vec<_>>()
+            .collect()
     }
 
-    fn query_type(&self) -> TypeType<S> {
+    #[graphql(name = "queryType")]
+    fn query_type_(&self) -> TypeType<S> {
         self.query_type()
     }
 
-    fn mutation_type(&self) -> Option<TypeType<S>> {
+    #[graphql(name = "mutationType")]
+    fn mutation_type_(&self) -> Option<TypeType<S>> {
         self.mutation_type()
     }
 
-    fn subscription_type(&self) -> Option<TypeType<S>> {
+    #[graphql(name = "subscriptionType")]
+    fn subscription_type_(&self) -> Option<TypeType<S>> {
         self.subscription_type()
     }
 
@@ -172,43 +171,37 @@ where
     }
 }
 
-#[crate::graphql_object(
+#[graphql_object(
     name = "__Type"
-    Context = SchemaType<'a, S>,
-    Scalar = S,
+    context = SchemaType<'a, S>,
+    scalar = S,
     internal,
-    // FIXME: make this redundant.
-    noasync,
 )]
-impl<'a, S> TypeType<'a, S>
-where
-    S: crate::ScalarValue + 'a,
-{
+impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
     fn name(&self) -> Option<&str> {
-        match *self {
+        match self {
             TypeType::Concrete(t) => t.name(),
             _ => None,
         }
     }
 
-    fn description(&self) -> Option<&String> {
-        match *self {
+    fn description(&self) -> Option<&str> {
+        match self {
             TypeType::Concrete(t) => t.description(),
             _ => None,
         }
     }
 
     fn kind(&self) -> TypeKind {
-        match *self {
+        match self {
             TypeType::Concrete(t) => t.type_kind(),
             TypeType::List(..) => TypeKind::List,
             TypeType::NonNull(_) => TypeKind::NonNull,
         }
     }
 
-    #[graphql(arguments(include_deprecated(default = false)))]
-    fn fields(&self, include_deprecated: bool) -> Option<Vec<&Field<S>>> {
-        match *self {
+    fn fields(&self, #[graphql(default)] include_deprecated: bool) -> Option<Vec<&Field<S>>> {
+        match self {
             TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. }))
             | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some(
                 fields
@@ -221,53 +214,53 @@ where
         }
     }
 
-    fn of_type(&self) -> Option<&Box<TypeType<S>>> {
-        match *self {
+    fn of_type(&self) -> Option<&TypeType<S>> {
+        match self {
             TypeType::Concrete(_) => None,
-            TypeType::List(ref l, _) | TypeType::NonNull(ref l) => Some(l),
+            TypeType::List(l, _) | TypeType::NonNull(l) => Some(&*l),
         }
     }
 
-    fn input_fields(&self) -> Option<&Vec<Argument<S>>> {
-        match *self {
+    fn input_fields(&self) -> Option<&[Argument<S>]> {
+        match self {
             TypeType::Concrete(&MetaType::InputObject(InputObjectMeta {
                 ref input_fields,
                 ..
-            })) => Some(input_fields),
+            })) => Some(input_fields.as_slice()),
             _ => None,
         }
     }
 
-    fn interfaces(&self, schema: &SchemaType<'a, S>) -> Option<Vec<TypeType<S>>> {
-        match *self {
+    fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option<Vec<TypeType<'s, S>>> {
+        match self {
             TypeType::Concrete(&MetaType::Object(ObjectMeta {
                 ref interface_names,
                 ..
             })) => Some(
                 interface_names
                     .iter()
-                    .filter_map(|n| schema.type_by_name(n))
+                    .filter_map(|n| context.type_by_name(n))
                     .collect(),
             ),
             _ => None,
         }
     }
 
-    fn possible_types(&self, schema: &SchemaType<'a, S>) -> Option<Vec<TypeType<S>>> {
-        match *self {
+    fn possible_types<'s>(&self, context: &'s SchemaType<'a, S>) -> Option<Vec<TypeType<'s, S>>> {
+        match self {
             TypeType::Concrete(&MetaType::Union(UnionMeta {
                 ref of_type_names, ..
             })) => Some(
                 of_type_names
                     .iter()
-                    .filter_map(|tn| schema.type_by_name(tn))
+                    .filter_map(|tn| context.type_by_name(tn))
                     .collect(),
             ),
             TypeType::Concrete(&MetaType::Interface(InterfaceMeta {
                 name: ref iface_name,
                 ..
             })) => Some(
-                schema
+                context
                     .concrete_type_list()
                     .iter()
                     .filter_map(|&ct| {
@@ -278,7 +271,7 @@ where
                         }) = *ct
                         {
                             if interface_names.contains(&iface_name.to_string()) {
-                                schema.type_by_name(name)
+                                context.type_by_name(name)
                             } else {
                                 None
                             }
@@ -292,9 +285,8 @@ where
         }
     }
 
-    #[graphql(arguments(include_deprecated(default = false)))]
-    fn enum_values(&self, include_deprecated: bool) -> Option<Vec<&EnumValue>> {
-        match *self {
+    fn enum_values(&self, #[graphql(default)] include_deprecated: bool) -> Option<Vec<&EnumValue>> {
+        match self {
             TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some(
                 values
                     .iter()
@@ -306,24 +298,20 @@ where
     }
 }
 
-#[crate::graphql_object(
+#[graphql_object(
     name = "__Field",
-    Context = SchemaType<'a, S>,
-    Scalar = S,
+    context = SchemaType<'a, S>,
+    scalar = S,
     internal,
-    // FIXME: make this redundant.
-    noasync,
 )]
-impl<'a, S> Field<'a, S>
-where
-    S: crate::ScalarValue + 'a,
-{
+impl<'a, S: ScalarValue + 'a> Field<'a, S> {
     fn name(&self) -> String {
         self.name.clone().into()
     }
 
-    fn description(&self) -> &Option<String> {
-        &self.description
+    #[graphql(name = "description")]
+    fn description_(&self) -> Option<&str> {
+        self.description.as_deref()
     }
 
     fn args(&self) -> Vec<&Argument<S>> {
@@ -333,7 +321,7 @@ where
     }
 
     #[graphql(name = "type")]
-    fn _type(&self, context: &SchemaType<'a, S>) -> TypeType<S> {
+    fn type_<'s>(&self, context: &'s SchemaType<'a, S>) -> TypeType<'s, S> {
         context.make_type(&self.field_type)
     }
 
@@ -341,94 +329,79 @@ where
         self.deprecation_status.is_deprecated()
     }
 
-    fn deprecation_reason(&self) -> Option<&String> {
+    fn deprecation_reason(&self) -> Option<&str> {
         self.deprecation_status.reason()
     }
 }
 
-#[crate::graphql_object(
+#[graphql_object(
     name = "__InputValue",
-    Context = SchemaType<'a, S>,
-    Scalar = S,
+    context = SchemaType<'a, S>,
+    scalar = S,
     internal,
-    // FIXME: make this redundant.
-    noasync,
 )]
-impl<'a, S> Argument<'a, S>
-where
-    S: crate::ScalarValue + 'a,
-{
-    fn name(&self) -> &String {
+impl<'a, S: ScalarValue + 'a> Argument<'a, S> {
+    fn name(&self) -> &str {
         &self.name
     }
 
-    fn description(&self) -> &Option<String> {
-        &self.description
+    #[graphql(name = "description")]
+    fn description_(&self) -> Option<&str> {
+        self.description.as_deref()
     }
 
     #[graphql(name = "type")]
-    fn _type(&self, context: &SchemaType<'a, S>) -> TypeType<S> {
+    fn type_<'s>(&self, context: &'s SchemaType<'a, S>) -> TypeType<'s, S> {
         context.make_type(&self.arg_type)
     }
 
-    fn default_value(&self) -> Option<String> {
-        self.default_value.as_ref().map(|v| format!("{}", v))
+    #[graphql(name = "defaultValue")]
+    fn default_value_(&self) -> Option<String> {
+        self.default_value.as_ref().map(ToString::to_string)
     }
 }
 
-#[crate::graphql_object(
-    name = "__EnumValue",
-    Scalar = S,
-    internal,
-    // FIXME: make this redundant.
-    noasync,
-)]
-impl<'a, S> EnumValue
-where
-    S: crate::ScalarValue + 'a,
-{
-    fn name(&self) -> &String {
+#[graphql_object(name = "__EnumValue", internal)]
+impl EnumValue {
+    fn name(&self) -> &str {
         &self.name
     }
 
-    fn description(&self) -> &Option<String> {
-        &self.description
+    #[graphql(name = "description")]
+    fn description_(&self) -> Option<&str> {
+        self.description.as_deref()
     }
 
     fn is_deprecated(&self) -> bool {
         self.deprecation_status.is_deprecated()
     }
 
-    fn deprecation_reason(&self) -> Option<&String> {
+    fn deprecation_reason(&self) -> Option<&str> {
         self.deprecation_status.reason()
     }
 }
 
-#[crate::graphql_object(
+#[graphql_object(
     name = "__Directive",
-    Context = SchemaType<'a, S>,
-    Scalar = S,
+    context = SchemaType<'a, S>,
+    scalar = S,
     internal,
-    // FIXME: make this redundant.
-    noasync,
 )]
-impl<'a, S> DirectiveType<'a, S>
-where
-    S: crate::ScalarValue + 'a,
-{
-    fn name(&self) -> &String {
+impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> {
+    fn name(&self) -> &str {
         &self.name
     }
 
-    fn description(&self) -> &Option<String> {
-        &self.description
+    #[graphql(name = "description")]
+    fn description_(&self) -> Option<&str> {
+        self.description.as_deref()
     }
 
-    fn locations(&self) -> &Vec<DirectiveLocation> {
+    fn locations(&self) -> &[DirectiveLocation] {
         &self.locations
     }
 
-    fn args(&self) -> &Vec<Argument<S>> {
+    fn args(&self) -> &[Argument<S>] {
         &self.arguments
     }
 
diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs
index 1b351a215..60979a409 100644
--- a/juniper/src/types/base.rs
+++ b/juniper/src/types/base.rs
@@ -105,7 +105,7 @@ where
     /// the `InputValue` will be converted into the type `T`.
     ///
     /// Returns `Some` if the argument is present _and_ type conversion
-    /// succeeeds.
+    /// succeeds.
     pub fn get<T>(&self, key: &str) -> Option<T>
     where
         T: FromInputValue<S>,
@@ -384,6 +384,8 @@ pub type DynGraphQLValue<S, C, TI> =
 ///     }
 /// }
 /// ```
+///
+/// [3]: https://spec.graphql.org/June2018/#sec-Objects
 pub trait GraphQLType<S = DefaultScalarValue>: GraphQLValue<S>
 where
     S: ScalarValue,
diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs
index 7c07f4aae..b04a41906 100644
--- a/juniper/src/types/marker.rs
+++ b/juniper/src/types/marker.rs
@@ -9,25 +9,33 @@ use std::sync::Arc;
 
 use crate::{GraphQLType, ScalarValue};
 
-/// Maker object for GraphQL objects.
+/// Maker trait for [GraphQL objects][1].
 ///
-/// This trait extends the GraphQLType and is only used to mark
-/// object. 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 an
-/// GraphQL objects. Other types (scalars, enums, and input objects)
-/// are not allowed.
-pub trait GraphQLObjectType<S: ScalarValue>: GraphQLType<S> {
+/// This trait extends the [`GraphQLType`] and is only used to mark an [object][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 objects][1]. Other types ([scalars][2], [enums][3], [interfaces][4], [input objects][5]
+/// and [unions][6]) are not allowed.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Objects
+/// [2]: https://spec.graphql.org/June2018/#sec-Scalars
+/// [3]: https://spec.graphql.org/June2018/#sec-Enums
+/// [4]: https://spec.graphql.org/June2018/#sec-Interfaces
+/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects
+/// [6]: https://spec.graphql.org/June2018/#sec-Unions
+pub trait GraphQLObject<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.
+    /// 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() {}
 }
 
-impl<'a, S, T> GraphQLObjectType<S> for &T
+impl<'a, S, T> GraphQLObject<S> for &T
 where
-    T: GraphQLObjectType<S> + ?Sized,
+    T: GraphQLObject<S> + ?Sized,
     S: ScalarValue,
 {
     #[inline]
@@ -36,9 +44,9 @@ where
     }
 }
 
-impl<S, T> GraphQLObjectType<S> for Box<T>
+impl<S, T> GraphQLObject<S> for Box<T>
 where
-    T: GraphQLObjectType<S> + ?Sized,
+    T: GraphQLObject<S> + ?Sized,
     S: ScalarValue,
 {
     #[inline]
@@ -47,9 +55,9 @@ where
     }
 }
 
-impl<S, T> GraphQLObjectType<S> for Arc<T>
+impl<S, T> GraphQLObject<S> for Arc<T>
 where
-    T: GraphQLObjectType<S> + ?Sized,
+    T: GraphQLObject<S> + ?Sized,
     S: ScalarValue,
 {
     #[inline]
diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs
index 0e3659fce..e463f74d2 100644
--- a/juniper/src/types/nullable.rs
+++ b/juniper/src/types/nullable.rs
@@ -290,8 +290,8 @@ where
         }
     }
 
-    fn from_implicit_null() -> Self {
-        Self::ImplicitNull
+    fn from_implicit_null() -> Option<Self> {
+        Some(Self::ImplicitNull)
     }
 }
 
diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs
index 2b5981e4d..a6c28fa75 100644
--- a/juniper/src/types/subscriptions.rs
+++ b/juniper/src/types/subscriptions.rs
@@ -82,6 +82,8 @@ where
 ///
 /// It can be treated as [`futures::Stream`] yielding [`GraphQLResponse`]s in
 /// server integration crates.
+///
+/// [`GraphQLResponse`]: crate::http::GraphQLResponse
 pub trait SubscriptionConnection<S>: futures::Stream<Item = ExecutionOutput<S>> {}
 
 /// Extension of [`GraphQLValue`] trait with asynchronous [subscription][1] execution logic.
diff --git a/juniper_actix/examples/actix_server.rs b/juniper_actix/examples/actix_server.rs
index 7a9638075..38981a666 100644
--- a/juniper_actix/examples/actix_server.rs
+++ b/juniper_actix/examples/actix_server.rs
@@ -71,12 +71,15 @@ impl juniper::Context for Database {}
 struct Query;
 #[graphql_object(context = Database)]
 impl Query {
-    fn apiVersion() -> String {
-        "1.0".to_string()
+    fn api_version() -> &'static str {
+        "1.0"
     }
-    #[graphql(arguments(id(description = "id of the user")))]
-    fn user(database: &Database, id: i32) -> Option<&User> {
-        database.get_user(&id)
+
+    fn user(
+        context: &Database,
+        #[graphql(description = "id of the user")] id: i32,
+    ) -> Option<&User> {
+        context.get_user(&id)
     }
 }
 
diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs
new file mode 100644
index 000000000..0c6d16a94
--- /dev/null
+++ b/juniper_codegen/src/common/field/arg.rs
@@ -0,0 +1,452 @@
+//! Common functions, definitions and extensions for parsing and code generation
+//! of [GraphQL arguments][1]
+//!
+//! [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments.
+
+use std::mem;
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{
+    ext::IdentExt as _,
+    parse::{Parse, ParseStream},
+    spanned::Spanned,
+    token,
+};
+
+use crate::{
+    common::{
+        parse::{
+            attr::{err, OptionExt as _},
+            ParseBufferExt as _, TypeExt as _,
+        },
+        scalar,
+    },
+    result::GraphQLScope,
+    util::{filter_attrs, path_eq_single, span_container::SpanContainer, RenameRule},
+};
+
+/// Available metadata (arguments) behind `#[graphql]` attribute placed on a
+/// method argument, when generating code for [GraphQL argument][1].
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+#[derive(Debug, Default)]
+pub(crate) struct Attr {
+    /// Explicitly specified name of a [GraphQL argument][1] represented by this
+    /// method argument.
+    ///
+    /// If [`None`], then `camelCased` Rust argument name is used by default.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    pub(crate) 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
+    pub(crate) 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`]
+    /// value is used.
+    ///
+    /// If [`None`], 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
+    pub(crate) 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
+    pub(crate) context: Option<SpanContainer<syn::Ident>>,
+
+    /// Explicitly specified marker indicating that this method argument doesn't
+    /// represent a [GraphQL argument][1], but is an [`Executor`] being injected
+    /// into a [GraphQL field][2] resolving function.
+    ///
+    /// If absent, then the method argument still is considered as [`Executor`]
+    /// 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
+    pub(crate) executor: Option<SpanContainer<syn::Ident>>,
+}
+
+impl Parse for Attr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = 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>()?;
+                    out.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>()?;
+                    out.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>()?);
+                    }
+                    out.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();
+                    out.context
+                        .replace(SpanContainer::new(span, Some(span), ident))
+                        .none_or_else(|_| err::dup_arg(span))?
+                }
+                "exec" | "executor" => {
+                    let span = ident.span();
+                    out.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(out)
+    }
+}
+
+impl Attr {
+    /// Tries to merge two [`Attr`]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 [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a function argument.
+    pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let attr = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if let Some(context) = &attr.context {
+            if attr.name.is_some()
+                || attr.description.is_some()
+                || attr.default.is_some()
+                || attr.executor.is_some()
+            {
+                return Err(syn::Error::new(
+                    context.span(),
+                    "`context` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        if let Some(executor) = &attr.executor {
+            if attr.name.is_some()
+                || attr.description.is_some()
+                || attr.default.is_some()
+                || attr.context.is_some()
+            {
+                return Err(syn::Error::new(
+                    executor.span(),
+                    "`executor` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        Ok(attr)
+    }
+
+    /// Checks whether this [`Attr`] doesn't contain arguments related to an
+    /// [`OnField`] argument.
+    #[must_use]
+    fn ensure_no_regular_arguments(&self) -> syn::Result<()> {
+        if let Some(span) = &self.name {
+            return Err(Self::err_disallowed(&span, "name"));
+        }
+        if let Some(span) = &self.description {
+            return Err(Self::err_disallowed(&span, "description"));
+        }
+        if let Some(span) = &self.default {
+            return Err(Self::err_disallowed(&span, "default"));
+        }
+        Ok(())
+    }
+
+    /// Emits "argument is not allowed" [`syn::Error`] for the given `arg`
+    /// pointing to the given `span`.
+    #[must_use]
+    fn err_disallowed<S: Spanned>(span: &S, arg: &str) -> syn::Error {
+        syn::Error::new(
+            span.span(),
+            format!(
+                "attribute argument `#[graphql({} = ...)]` is not allowed here",
+                arg,
+            ),
+        )
+    }
+}
+
+/// Representation of a [GraphQL field argument][1] for code generation.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+#[derive(Debug)]
+pub(crate) struct OnField {
+    /// Rust type that this [GraphQL field argument][1] is represented by.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    pub(crate) ty: syn::Type,
+
+    /// Name of this [GraphQL field argument][2] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    pub(crate) name: String,
+
+    /// [Description][2] of this [GraphQL field argument][1] to put into GraphQL
+    /// schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
+    pub(crate) description: Option<String>,
+
+    /// Default value of this [GraphQL field argument][1] in GraphQL schema.
+    ///
+    /// If outer [`Option`] is [`None`], then this [argument][1] is a
+    /// [required][2] one.
+    ///
+    /// If inner [`Option`] is [`None`], then the [`Default`] value is used.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments
+    pub(crate) default: Option<Option<syn::Expr>>,
+}
+
+/// Possible kinds of Rust method arguments for code generation.
+#[derive(Debug)]
+pub(crate) enum OnMethod {
+    /// Regular [GraphQL field argument][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments
+    Regular(OnField),
+
+    /// [`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 OnMethod {
+    /// Returns this argument as the one [`OnField`], if it represents the one.
+    #[must_use]
+    pub(crate) fn as_regular(&self) -> Option<&OnField> {
+        if let Self::Regular(arg) = self {
+            Some(arg)
+        } else {
+            None
+        }
+    }
+
+    /// Returns [`syn::Type`] of this [`OnMethod::Context`], if it represents
+    /// the one.
+    #[must_use]
+    pub(crate) fn context_ty(&self) -> Option<&syn::Type> {
+        if let Self::Context(ty) = self {
+            Some(ty)
+        } else {
+            None
+        }
+    }
+
+    /// Returns generated code for the [`marker::IsOutputType::mark`] method,
+    /// which performs static checks for this argument, if it represents an
+    /// [`OnField`] one.
+    ///
+    /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark
+    #[must_use]
+    pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> Option<TokenStream> {
+        let ty = &self.as_regular()?.ty;
+        Some(quote! {
+            <#ty as ::juniper::marker::IsInputType<#scalar>>::mark();
+        })
+    }
+
+    /// Returns generated code for the [`GraphQLType::meta`] method, which
+    /// registers this argument in [`Registry`], if it represents an [`OnField`]
+    /// argument.
+    ///
+    /// [`GraphQLType::meta`]: juniper::GraphQLType::meta
+    /// [`Registry`]: juniper::Registry
+    #[must_use]
+    pub(crate) 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 [`OnMethod`] argument to be passed into
+    /// a trait method call.
+    ///
+    /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
+    #[must_use]
+    pub(crate) fn method_resolve_field_tokens(&self, scalar: &scalar::Type) -> 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)
+                        .or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null)
+                        .expect(#err_text)
+                }
+            }
+
+            Self::Context(_) => quote! {
+                ::juniper::FromContext::from(executor.context())
+            },
+
+            Self::Executor => quote! { &executor },
+        }
+    }
+
+    /// Parses an [`OnMethod`] argument from the given Rust method argument
+    /// definition.
+    ///
+    /// Returns [`None`] if parsing fails and emits parsing errors into the
+    /// given `scope`.
+    pub(crate) fn parse(
+        argument: &mut syn::PatType,
+        renaming: &RenameRule,
+        scope: &GraphQLScope,
+    ) -> Option<Self> {
+        let orig_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"))
+            .collect();
+
+        let attr = Attr::from_attrs("graphql", &orig_attrs)
+            .map_err(|e| proc_macro_error::emit_error!(e))
+            .ok()?;
+
+        if attr.context.is_some() {
+            return Some(Self::Context(argument.ty.unreferenced().clone()));
+        }
+        if attr.executor.is_some() {
+            return Some(Self::Executor);
+        }
+        if let syn::Pat::Ident(name) = &*argument.pat {
+            let arg = match name.ident.unraw().to_string().as_str() {
+                "context" | "ctx" | "_context" | "_ctx" => {
+                    Some(Self::Context(argument.ty.unreferenced().clone()))
+                }
+                "executor" | "_executor" => Some(Self::Executor),
+                _ => None,
+            };
+            if arg.is_some() {
+                attr.ensure_no_regular_arguments()
+                    .map_err(|e| scope.error(e).emit())
+                    .ok()?;
+                return arg;
+            }
+        }
+
+        let name = if let Some(name) = attr.name.as_ref() {
+            name.as_ref().value()
+        } else if let syn::Pat::Ident(name) = &*argument.pat {
+            renaming.apply(&name.ident.unraw().to_string())
+        } else {
+            scope
+                .custom(
+                    argument.pat.span(),
+                    "method argument should be declared as a single identifier",
+                )
+                .note(String::from(
+                    "use `#[graphql(name = ...)]` attribute to specify custom argument's \
+                     name without requiring it being a single identifier",
+                ))
+                .emit();
+            return None;
+        };
+        if name.starts_with("__") {
+            scope.no_double_underscore(
+                attr.name
+                    .as_ref()
+                    .map(SpanContainer::span_ident)
+                    .unwrap_or_else(|| argument.pat.span()),
+            );
+            return None;
+        }
+
+        Some(Self::Regular(OnField {
+            name,
+            ty: argument.ty.as_ref().clone(),
+            description: attr.description.as_ref().map(|d| d.as_ref().value()),
+            default: attr.default.as_ref().map(|v| v.as_ref().clone()),
+        }))
+    }
+}
diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs
new file mode 100644
index 000000000..a627e444b
--- /dev/null
+++ b/juniper_codegen/src/common/field/mod.rs
@@ -0,0 +1,563 @@
+//! Common functions, definitions and extensions for parsing and code generation
+//! of [GraphQL fields][1]
+//!
+//! [1]: https://spec.graphql.org/June2018/#sec-Language.Fields.
+
+pub(crate) mod arg;
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned as _,
+    token,
+};
+
+use crate::{
+    common::{
+        gen,
+        parse::{
+            attr::{err, OptionExt as _},
+            ParseBufferExt as _,
+        },
+        scalar,
+    },
+    util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer},
+};
+
+pub(crate) use self::arg::OnMethod as MethodArgument;
+
+/// Available metadata (arguments) behind `#[graphql]` attribute placed on a
+/// [GraphQL field][1] definition.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+#[derive(Debug, Default)]
+pub(crate) struct Attr {
+    /// Explicitly specified name of this [GraphQL field][1].
+    ///
+    /// If [`None`], then `camelCased` Rust method name is used by default.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) name: Option<SpanContainer<syn::LitStr>>,
+
+    /// Explicitly specified [description][2] of this [GraphQL field][1].
+    ///
+    /// If [`None`], 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
+    pub(crate) description: Option<SpanContainer<syn::LitStr>>,
+
+    /// Explicitly specified [deprecation][2] of this [GraphQL field][1].
+    ///
+    /// If [`None`], 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
+    pub(crate) deprecated: Option<SpanContainer<Option<syn::LitStr>>>,
+
+    /// Explicitly specified marker indicating that this method (or struct
+    /// field) should be omitted by code generation and not considered as the
+    /// [GraphQL field][1] definition.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) 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 external downcasting
+    /// function.
+    ///
+    /// Omit using this field if you're generating code for [GraphQL object][2].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) downcast: Option<SpanContainer<syn::Ident>>,
+}
+
+impl Parse for Attr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = 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>()?;
+                    out.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>()?;
+                    out.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>()?);
+                    }
+                    out.deprecated
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            reason.as_ref().map(|r| r.span()),
+                            reason,
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "ignore" | "skip" => out
+                    .ignore
+                    .replace(SpanContainer::new(ident.span(), None, ident.clone()))
+                    .none_or_else(|_| err::dup_arg(&ident))?,
+                "downcast" => out
+                    .downcast
+                    .replace(SpanContainer::new(ident.span(), None, ident.clone()))
+                    .none_or_else(|_| err::dup_arg(&ident))?,
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
+                }
+            }
+            input.try_parse::<token::Comma>()?;
+        }
+        Ok(out)
+    }
+}
+
+impl Attr {
+    /// Tries to merge two [`Attrs`]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 [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a [GraphQL field][1] definition.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let mut attr = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if let Some(ignore) = &attr.ignore {
+            if attr.name.is_some()
+                || attr.description.is_some()
+                || attr.deprecated.is_some()
+                || attr.downcast.is_some()
+            {
+                return Err(syn::Error::new(
+                    ignore.span(),
+                    "`ignore` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        if let Some(downcast) = &attr.downcast {
+            if attr.name.is_some()
+                || attr.description.is_some()
+                || attr.deprecated.is_some()
+                || attr.ignore.is_some()
+            {
+                return Err(syn::Error::new(
+                    downcast.span(),
+                    "`downcast` attribute argument is not composable with any other arguments",
+                ));
+            }
+        }
+
+        if attr.description.is_none() {
+            attr.description = get_doc_comment(attrs).map(|sc| {
+                let span = sc.span_ident();
+                sc.map(|desc| syn::LitStr::new(&desc, span))
+            });
+        }
+
+        if attr.deprecated.is_none() {
+            attr.deprecated = get_deprecated(attrs).map(|sc| {
+                let span = sc.span_ident();
+                sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span)))
+            });
+        }
+
+        Ok(attr)
+    }
+}
+
+/// Representation of a [GraphQL field][1] for code generation.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+#[derive(Debug)]
+pub(crate) struct Definition {
+    /// Rust type that this [GraphQL field][1] is represented by (method return
+    /// type or struct field type).
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) ty: syn::Type,
+
+    /// Name of this [GraphQL field][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) name: String,
+
+    /// [Description][2] of this [GraphQL field][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
+    pub(crate) description: Option<String>,
+
+    /// [Deprecation][2] of this [GraphQL field][1] to put into GraphQL schema.
+    ///
+    /// If inner [`Option`] is [`None`], then deprecation has no message
+    /// attached.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation
+    pub(crate) deprecated: Option<Option<String>>,
+
+    /// Ident of the Rust method (or struct field) representing this
+    /// [GraphQL field][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) ident: syn::Ident,
+
+    /// Rust [`MethodArgument`]s required to call the method representing this
+    /// [GraphQL field][1].
+    ///
+    /// If [`None`] then this [GraphQL field][1] is represented by a struct
+    /// field.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) arguments: Option<Vec<MethodArgument>>,
+
+    /// Indicator whether the Rust method representing this [GraphQL field][1]
+    /// has a [`syn::Receiver`].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) has_receiver: bool,
+
+    /// Indicator whether this [GraphQL field][1] should be resolved
+    /// asynchronously.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) is_async: bool,
+}
+
+impl Definition {
+    /// Indicates whether this [GraphQL field][1] is represented by a method,
+    /// not a struct field.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn is_method(&self) -> bool {
+        self.arguments.is_some()
+    }
+
+    /// Returns generated code that panics about unknown [GraphQL field][1]
+    /// tried to be resolved in the [`GraphQLValue::resolve_field`] method.
+    ///
+    /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn method_resolve_field_panic_no_field_tokens(scalar: &scalar::Type) -> TokenStream {
+        quote! {
+            panic!(
+                "Field `{}` not found on type `{}`",
+                field,
+                <Self as ::juniper::GraphQLType<#scalar>>::name(info).unwrap(),
+            )
+        }
+    }
+
+    /// Returns generated code that panics about [GraphQL fields][1] tried to be
+    /// resolved asynchronously in the [`GraphQLValue::resolve_field`] method
+    /// (which is synchronous itself).
+    ///
+    /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn method_resolve_field_panic_async_field_tokens(
+        field_names: &[&str],
+        scalar: &scalar::Type,
+    ) -> TokenStream {
+        quote! {
+            #( #field_names )|* => panic!(
+                "Tried to resolve async field `{}` on type `{}` with a sync resolver",
+                field,
+                <Self as ::juniper::GraphQLType<#scalar>>::name(info).unwrap(),
+            ),
+        }
+    }
+
+    /// Returns generated code for the [`marker::IsOutputType::mark`] method,
+    /// which performs static checks for this [GraphQL field][1].
+    ///
+    /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn method_mark_tokens(
+        &self,
+        infer_result: bool,
+        scalar: &scalar::Type,
+    ) -> TokenStream {
+        let args_marks = self
+            .arguments
+            .iter()
+            .flat_map(|args| args.iter().filter_map(|a| a.method_mark_tokens(scalar)));
+
+        let ty = &self.ty;
+        let mut ty = quote! { #ty };
+        if infer_result {
+            ty = quote! {
+                <#ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item
+            };
+        }
+        let resolved_ty = quote! {
+            <#ty as ::juniper::IntoResolvable<
+                '_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
+            >>::Type
+        };
+
+        quote! {
+            #( #args_marks )*
+            <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
+        }
+    }
+
+    /// Returns generated code for the [`GraphQLType::meta`] method, which
+    /// registers this [GraphQL field][1] in [`Registry`].
+    ///
+    /// [`GraphQLType::meta`]: juniper::GraphQLType::meta
+    /// [`Registry`]: juniper::Registry
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn method_meta_tokens(
+        &self,
+        extract_stream_type: Option<&scalar::Type>,
+    ) -> TokenStream {
+        let (name, ty) = (&self.name, &self.ty);
+        let mut ty = quote! { #ty };
+        if let Some(scalar) = extract_stream_type {
+            ty = quote! {
+                <#ty as ::juniper::ExtractTypeFromStream<_, #scalar>>::Item
+            };
+        }
+
+        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 args = self
+            .arguments
+            .iter()
+            .flat_map(|args| args.iter().filter_map(MethodArgument::method_meta_tokens));
+
+        quote! {
+            registry.field_convert::<#ty, _, Self::Context>(#name, info)
+                #( #args )*
+                #description
+                #deprecated
+        }
+    }
+
+    /// Returns generated code for the [`GraphQLValue::resolve_field`][0]
+    /// method, which resolves this [GraphQL field][1] synchronously.
+    ///
+    /// Returns [`None`] if this [`Definition::is_async`].
+    ///
+    /// [0]: juniper::GraphQLValue::resolve_field
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn method_resolve_field_tokens(
+        &self,
+        scalar: &scalar::Type,
+        trait_ty: Option<&syn::Type>,
+    ) -> Option<TokenStream> {
+        if self.is_async {
+            return None;
+        }
+
+        let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident);
+
+        let res = if self.is_method() {
+            let args = self
+                .arguments
+                .as_ref()
+                .unwrap()
+                .iter()
+                .map(|arg| arg.method_resolve_field_tokens(scalar));
+
+            let rcv = self.has_receiver.then(|| {
+                quote! { self, }
+            });
+
+            if trait_ty.is_some() {
+                quote! { <Self as #trait_ty>::#ident(#rcv #( #args ),*) }
+            } else {
+                quote! { Self::#ident(#rcv #( #args ),*) }
+            }
+        } else {
+            ty = parse_quote! { _ };
+            quote! { &self.#ident }
+        };
+
+        let resolving_code = gen::sync_resolving_code();
+
+        Some(quote! {
+            #name => {
+                let res: #ty = #res;
+                #resolving_code
+            }
+        })
+    }
+
+    /// Returns generated code for the
+    /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves
+    /// this [GraphQL field][1] asynchronously.
+    ///
+    /// [0]: juniper::GraphQLValueAsync::resolve_field_async
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    #[must_use]
+    pub(crate) fn method_resolve_field_async_tokens(
+        &self,
+        scalar: &scalar::Type,
+        trait_ty: Option<&syn::Type>,
+    ) -> TokenStream {
+        let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident);
+
+        let mut fut = if self.is_method() {
+            let args = self
+                .arguments
+                .as_ref()
+                .unwrap()
+                .iter()
+                .map(|arg| arg.method_resolve_field_tokens(scalar));
+
+            let rcv = self.has_receiver.then(|| {
+                quote! { self, }
+            });
+
+            if trait_ty.is_some() {
+                quote! { <Self as #trait_ty>::#ident(#rcv #( #args ),*) }
+            } else {
+                quote! { Self::#ident(#rcv #( #args ),*) }
+            }
+        } else {
+            ty = parse_quote! { _ };
+            quote! { &self.#ident }
+        };
+        if !self.is_async {
+            fut = quote! { ::juniper::futures::future::ready(#fut) };
+        }
+
+        let resolving_code = gen::async_resolving_code(Some(&ty));
+
+        quote! {
+            #name => {
+                let fut = #fut;
+                #resolving_code
+            }
+        }
+    }
+
+    /// Returns generated code for the
+    /// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which
+    /// resolves this [GraphQL field][1] as [subscription][2].
+    ///
+    /// [0]: juniper::GraphQLSubscriptionValue::resolve_field_into_stream
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    /// [2]: https://spec.graphql.org/June2018/#sec-Subscription
+    #[must_use]
+    pub(crate) fn method_resolve_field_into_stream_tokens(
+        &self,
+        scalar: &scalar::Type,
+    ) -> TokenStream {
+        let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident);
+
+        let mut fut = if self.is_method() {
+            let args = self
+                .arguments
+                .as_ref()
+                .unwrap()
+                .iter()
+                .map(|arg| arg.method_resolve_field_tokens(scalar));
+
+            let rcv = self.has_receiver.then(|| {
+                quote! { self, }
+            });
+
+            quote! { Self::#ident(#rcv #( #args ),*) }
+        } else {
+            ty = parse_quote! { _ };
+            quote! { &self.#ident }
+        };
+        if !self.is_async {
+            fut = quote! { ::juniper::futures::future::ready(#fut) };
+        }
+
+        quote! {
+            #name => {
+                ::juniper::futures::FutureExt::boxed(async move {
+                    let res: #ty = #fut.await;
+                    let res = ::juniper::IntoFieldResult::<_, #scalar>::into_result(res)?;
+                    let executor = executor.as_owned_executor();
+                    let stream = ::juniper::futures::StreamExt::then(res, move |res| {
+                        let executor = executor.clone();
+                        let res2: ::juniper::FieldResult<_, #scalar> =
+                            ::juniper::IntoResolvable::into(res, executor.context());
+                        async move {
+                            let ex = executor.as_executor();
+                            match res2 {
+                                Ok(Some((ctx, r))) => {
+                                    let sub = ex.replaced_context(ctx);
+                                    sub.resolve_with_ctx_async(&(), &r)
+                                        .await
+                                        .map_err(|e| ex.new_error(e))
+                                }
+                                Ok(None) => Ok(::juniper::Value::null()),
+                                Err(e) => Err(ex.new_error(e)),
+                            }
+                        }
+                    });
+                    Ok(::juniper::Value::Scalar::<
+                        ::juniper::ValuesStream::<#scalar>
+                    >(::juniper::futures::StreamExt::boxed(stream)))
+                })
+            }
+        }
+    }
+}
+
+/// Checks whether all [GraphQL fields][1] fields have different names.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
+#[must_use]
+pub(crate) fn all_different(fields: &[Definition]) -> bool {
+    let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect();
+    names.dedup();
+    names.len() == fields.len()
+}
diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs
index 88498c1e8..fd16d954d 100644
--- a/juniper_codegen/src/common/mod.rs
+++ b/juniper_codegen/src/common/mod.rs
@@ -1,89 +1,6 @@
 //! Common functions, definitions and extensions for code generation, used by this crate.
 
+pub(crate) mod field;
 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)
-    }
-}
+pub(crate) mod scalar;
diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs
index c3ec54f7c..cae19a015 100644
--- a/juniper_codegen/src/common/parse/mod.rs
+++ b/juniper_codegen/src/common/parse/mod.rs
@@ -95,19 +95,30 @@ impl<'a> ParseBufferExt for ParseBuffer<'a> {
 
 /// 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).
+    /// 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.
+    /// 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;
 
+    /// Iterates mutably over all the lifetime parameters of this [`syn::Type`]
+    /// with the given `func`tion.
+    fn lifetimes_iter_mut<F: FnMut(&mut syn::Lifetime)>(&mut self, func: &mut F);
+
+    /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except
+    /// the `'static` ones), making it suitable for using in contexts with
+    /// inferring.
     fn lifetimes_anonymized(&mut self);
+
+    /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any.
+    #[must_use]
+    fn topmost_ident(&self) -> Option<&syn::Ident>;
 }
 
 impl TypeExt for syn::Type {
@@ -125,19 +136,45 @@ impl TypeExt for syn::Type {
         }
     }
 
-    fn lifetimes_anonymized(&mut self) {
+    fn lifetimes_iter_mut<F: FnMut(&mut syn::Lifetime)>(&mut self, func: &mut F) {
         use syn::{GenericArgument as GA, Type as T};
 
+        fn iter_path<F: FnMut(&mut syn::Lifetime)>(path: &mut syn::Path, func: &mut F) {
+            for seg in path.segments.iter_mut() {
+                match &mut seg.arguments {
+                    syn::PathArguments::AngleBracketed(angle) => {
+                        for arg in angle.args.iter_mut() {
+                            match arg {
+                                GA::Lifetime(lt) => func(lt),
+                                GA::Type(ty) => ty.lifetimes_iter_mut(func),
+                                GA::Binding(b) => b.ty.lifetimes_iter_mut(func),
+                                GA::Constraint(_) | GA::Const(_) => {}
+                            }
+                        }
+                    }
+                    syn::PathArguments::Parenthesized(args) => {
+                        for ty in args.inputs.iter_mut() {
+                            ty.lifetimes_iter_mut(func)
+                        }
+                        if let syn::ReturnType::Type(_, ty) = &mut args.output {
+                            (&mut *ty).lifetimes_iter_mut(func)
+                        }
+                    }
+                    syn::PathArguments::None => {}
+                }
+            }
+        }
+
         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::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_iter_mut(func),
 
             T::Tuple(syn::TypeTuple { elems, .. }) => {
                 for ty in elems.iter_mut() {
-                    ty.lifetimes_anonymized();
+                    ty.lifetimes_iter_mut(func)
                 }
             }
 
@@ -145,11 +182,12 @@ impl TypeExt for syn::Type {
             | 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")
+                        syn::TypeParamBound::Lifetime(lt) => func(lt),
+                        syn::TypeParamBound::Trait(bound) => {
+                            if bound.lifetimes.is_some() {
+                                todo!("Iterating over HRTB lifetimes in trait is not yet supported")
+                            }
+                            iter_path(&mut bound.path, func)
                         }
                     }
                 }
@@ -157,43 +195,17 @@ impl TypeExt for syn::Type {
 
             T::Reference(ref_ty) => {
                 if let Some(lt) = ref_ty.lifetime.as_mut() {
-                    lt.ident = syn::Ident::new("_", Span::call_site());
+                    func(lt)
                 }
-                (&mut *ref_ty.elem).lifetimes_anonymized();
+                (&mut *ref_ty.elem).lifetimes_iter_mut(func)
             }
 
-            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 => {}
-                    }
-                }
-            }
+            T::Path(ty) => iter_path(&mut ty.path, func),
 
             // These types unlikely will be used as GraphQL types.
             T::BareFn(_) | T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) => {}
 
-            // Following the syn idiom for exhaustive matching on Type
+            // Following the syn idiom for exhaustive matching on Type:
             // https://github.com/dtolnay/syn/blob/master/src/ty.rs#L66-L88
             #[cfg(test)]
             T::__TestExhaustive(_) => unimplemented!(),
@@ -202,6 +214,32 @@ impl TypeExt for syn::Type {
             _ => {}
         }
     }
+
+    fn lifetimes_anonymized(&mut self) {
+        self.lifetimes_iter_mut(&mut |lt| {
+            if lt.ident != "_" && lt.ident != "static" {
+                lt.ident = syn::Ident::new("_", Span::call_site());
+            }
+        });
+    }
+
+    fn topmost_ident(&self) -> Option<&syn::Ident> {
+        match self.unparenthesized() {
+            syn::Type::Path(p) => Some(&p.path),
+            syn::Type::Reference(r) => match (&*r.elem).unparenthesized() {
+                syn::Type::Path(p) => Some(&p.path),
+                syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() {
+                    syn::TypeParamBound::Trait(b) => Some(&b.path),
+                    _ => None,
+                },
+                _ => None,
+            },
+            _ => None,
+        }?
+        .segments
+        .last()
+        .map(|s| &s.ident)
+    }
 }
 
 /// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing.
diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs
new file mode 100644
index 000000000..1569a5391
--- /dev/null
+++ b/juniper_codegen/src/common/scalar.rs
@@ -0,0 +1,173 @@
+//! Common functions, definitions and extensions for parsing and code generation
+//! related to [`ScalarValue`].
+//!
+//! [`ScalarValue`]: juniper::ScalarValue
+
+use proc_macro2::{Span, TokenStream};
+use quote::ToTokens;
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned,
+};
+
+/// Possible values of `#[graphql(scalar = ...)]` attribute.
+#[derive(Clone, Debug)]
+pub(crate) enum AttrValue {
+    /// Concrete Rust type (like `DefaultScalarValue`).
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    Concrete(syn::Type),
+
+    /// Generic Rust type parameter with a bound predicate
+    /// (like `S: ScalarValue + Send + Sync`).
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    Generic(syn::PredicateType),
+}
+
+impl Parse for AttrValue {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        if input.fork().parse::<syn::WherePredicate>().is_ok() {
+            let pred = input.parse().unwrap();
+            if let syn::WherePredicate::Type(p) = pred {
+                Ok(Self::Generic(p))
+            } else {
+                Err(syn::Error::new(
+                    pred.span(),
+                    "only type predicates are allowed here",
+                ))
+            }
+        } else {
+            input.parse::<syn::Type>().map(Self::Concrete)
+        }
+    }
+}
+
+impl Spanned for AttrValue {
+    fn span(&self) -> Span {
+        match self {
+            Self::Concrete(ty) => ty.span(),
+            Self::Generic(pred) => pred.span(),
+        }
+    }
+}
+
+/// [`ScalarValue`] parametrization of the code generation.
+///
+/// [`ScalarValue`]: juniper::ScalarValue
+#[derive(Clone, Debug)]
+pub(crate) enum Type {
+    /// 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 generic and is not specified
+    /// explicitly, or specified as bound predicate (like `S: ScalarValue + Send + Sync`).
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    ImplicitGeneric(Option<syn::PredicateType>),
+}
+
+impl ToTokens for Type {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.ty().to_tokens(into)
+    }
+}
+
+impl Type {
+    /// Indicates whether this [`Type`] is generic.
+    #[must_use]
+    pub(crate) fn is_generic(&self) -> bool {
+        matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_))
+    }
+
+    /// Indicates whether this [`Type`] is [`Type::ExplicitGeneric`].
+    #[must_use]
+    pub(crate) fn is_explicit_generic(&self) -> bool {
+        matches!(self, Self::ExplicitGeneric(_))
+    }
+
+    /// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`].
+    #[must_use]
+    pub(crate) fn is_implicit_generic(&self) -> bool {
+        matches!(self, Self::ImplicitGeneric(_))
+    }
+
+    /// Returns additional trait bounds behind this [`Type`], if any.
+    #[must_use]
+    pub(crate) fn bounds(&self) -> Option<syn::WherePredicate> {
+        if let Self::ImplicitGeneric(Some(pred)) = self {
+            Some(syn::WherePredicate::Type(pred.clone()))
+        } else {
+            None
+        }
+    }
+
+    /// Returns a type identifier which represents this [`Type`].
+    #[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(Some(pred)) => pred.bounded_ty.clone(),
+            Self::ImplicitGeneric(None) => parse_quote! { __S },
+        }
+    }
+
+    /// Returns a type parameter identifier that suits this [`Type`].
+    #[must_use]
+    pub(crate) fn generic_ty(&self) -> syn::Type {
+        match self {
+            Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param },
+            Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(),
+            Self::ImplicitGeneric(None) | Self::Concrete(_) => parse_quote! { __S },
+        }
+    }
+
+    /// Returns a default [`ScalarValue`] type that is compatible with this [`Type`].
+    ///
+    /// [`ScalarValue`]: juniper::ScalarValue
+    #[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 }
+            }
+        }
+    }
+
+    /// Parses [`Type`] from the given `explicit` [`AttrValue`] (if any),
+    /// checking whether it's contained in the giving `generics`.
+    #[must_use]
+    pub(crate) fn parse(explicit: Option<&AttrValue>, generics: &syn::Generics) -> Self {
+        match explicit {
+            Some(AttrValue::Concrete(scalar_ty)) => 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 == scalar_ty {
+                            return Some(&tp.ident);
+                        }
+                    }
+                    None
+                })
+                .map(|ident| Self::ExplicitGeneric(ident.clone()))
+                .unwrap_or_else(|| Self::Concrete(scalar_ty.clone())),
+            Some(AttrValue::Generic(pred)) => Self::ImplicitGeneric(Some(pred.clone())),
+            None => Self::ImplicitGeneric(None),
+        }
+    }
+}
diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs
deleted file mode 100644
index 6d0af8fa4..000000000
--- a/juniper_codegen/src/derive_object.rs
+++ /dev/null
@@ -1,137 +0,0 @@
-use crate::{
-    result::{GraphQLScope, UnsupportedAttribute},
-    util::{self, span_container::SpanContainer, RenameRule},
-};
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
-
-pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<TokenStream> {
-    let ast_span = ast.span();
-    let struct_fields = match ast.data {
-        Data::Struct(data) => match data.fields {
-            Fields::Named(fields) => fields.named,
-            _ => return Err(error.custom_error(ast_span, "only named fields are allowed")),
-        },
-        _ => return Err(error.custom_error(ast_span, "can only be applied to structs")),
-    };
-
-    // Parse attributes.
-    let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
-
-    let ident = &ast.ident;
-    let name = attrs
-        .name
-        .clone()
-        .map(SpanContainer::into_inner)
-        .unwrap_or_else(|| ident.unraw().to_string());
-
-    let fields = struct_fields
-        .into_iter()
-        .filter_map(|field| {
-            let span = field.span();
-            let field_attrs = match util::FieldAttributes::from_attrs(
-                &field.attrs,
-                util::FieldAttributeParseMode::Object,
-            ) {
-                Ok(attrs) => attrs,
-                Err(e) => {
-                    proc_macro_error::emit_error!(e);
-                    return None;
-                }
-            };
-
-            if field_attrs.skip.is_some() {
-                return None;
-            }
-
-            let field_name = &field.ident.unwrap();
-            let name = field_attrs
-                .name
-                .clone()
-                .map(SpanContainer::into_inner)
-                .unwrap_or_else(|| {
-                    attrs
-                        .rename
-                        .unwrap_or(RenameRule::CamelCase)
-                        .apply(&field_name.unraw().to_string())
-                });
-
-            if name.starts_with("__") {
-                error.no_double_underscore(if let Some(name) = field_attrs.name {
-                    name.span_ident()
-                } else {
-                    field_name.span()
-                });
-            }
-
-            if let Some(default) = field_attrs.default {
-                error.unsupported_attribute_within(
-                    default.span_ident(),
-                    UnsupportedAttribute::Default,
-                );
-            }
-
-            let resolver_code = quote!(
-                &self . #field_name
-            );
-
-            Some(util::GraphQLTypeDefinitionField {
-                name,
-                _type: field.ty,
-                args: Vec::new(),
-                description: field_attrs.description.map(SpanContainer::into_inner),
-                deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
-                resolver_code,
-                default: None,
-                is_type_inferred: true,
-                is_async: false,
-                span,
-            })
-        })
-        .collect::<Vec<_>>();
-
-    // Early abort after checking all fields
-    proc_macro_error::abort_if_dirty();
-
-    if let Some(duplicates) =
-        crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
-    {
-        error.duplicate(duplicates.iter());
-    }
-
-    if !attrs.is_internal && name.starts_with("__") {
-        error.no_double_underscore(if let Some(name) = attrs.name {
-            name.span_ident()
-        } else {
-            ident.span()
-        });
-    }
-
-    if fields.is_empty() {
-        error.not_empty(ast_span);
-    }
-
-    // Early abort after GraphQL properties
-    proc_macro_error::abort_if_dirty();
-
-    let definition = util::GraphQLTypeDefiniton {
-        name,
-        _type: syn::parse_str(&ast.ident.to_string()).unwrap(),
-        context: attrs.context.map(SpanContainer::into_inner),
-        scalar: attrs.scalar.map(SpanContainer::into_inner),
-        description: attrs.description.map(SpanContainer::into_inner),
-        fields,
-        generics: ast.generics,
-        interfaces: attrs
-            .interfaces
-            .into_iter()
-            .map(SpanContainer::into_inner)
-            .collect(),
-        include_type_generics: true,
-        generic_scalar: true,
-        no_async: attrs.no_async.is_some(),
-    };
-
-    Ok(definition.into_tokens())
-}
diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs
index 44187befa..2aedbb2ad 100644
--- a/juniper_codegen/src/graphql_interface/attr.rs
+++ b/juniper_codegen/src/graphql_interface/attr.rs
@@ -8,16 +8,17 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
 
 use crate::{
     common::{
+        field,
         parse::{self, TypeExt as _},
-        ScalarValueType,
+        scalar,
     },
     result::GraphQLScope,
-    util::{path_eq_single, span_container::SpanContainer, to_camel_case},
+    util::{path_eq_single, span_container::SpanContainer, RenameRule},
 };
 
 use super::{
-    inject_async_trait, ArgumentMeta, Definition, EnumType, Field, FieldArgument, ImplMeta,
-    Implementer, ImplementerDowncast, MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type,
+    inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast,
+    TraitAttr, TraitObjectType, Type,
 };
 
 /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
@@ -45,62 +46,42 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
 }
 
 /// Expands `#[graphql_interface]` macro placed on trait definition.
-pub fn expand_on_trait(
+fn expand_on_trait(
     attrs: Vec<syn::Attribute>,
     mut ast: syn::ItemTrait,
 ) -> syn::Result<TokenStream> {
-    let meta = TraitMeta::from_attrs("graphql_interface", &attrs)?;
+    let attr = TraitAttr::from_attrs("graphql_interface", &attrs)?;
 
     let trait_ident = &ast.ident;
     let trait_span = ast.span();
 
-    let name = meta
+    let name = attr
         .name
         .clone()
         .map(SpanContainer::into_inner)
         .unwrap_or_else(|| trait_ident.unraw().to_string());
-    if !meta.is_internal && name.starts_with("__") {
+    if !attr.is_internal && name.starts_with("__") {
         ERR.no_double_underscore(
-            meta.name
+            attr.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 scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
 
-    let mut implementers: Vec<_> = meta
+    let mut implementers: Vec<_> = attr
         .implementers
         .iter()
         .map(|ty| Implementer {
             ty: ty.as_ref().clone(),
             downcast: None,
-            context_ty: None,
+            context: None,
             scalar: scalar.clone(),
         })
         .collect();
-    for (ty, downcast) in &meta.external_downcasts {
+    for (ty, downcast) in &attr.external_downcasts {
         match implementers.iter_mut().find(|i| &i.ty == ty) {
             Some(impler) => {
                 impler.downcast = Some(ImplementerDowncast::External {
@@ -113,10 +94,16 @@ pub fn expand_on_trait(
 
     proc_macro_error::abort_if_dirty();
 
+    let renaming = attr
+        .rename_fields
+        .as_deref()
+        .copied()
+        .unwrap_or(RenameRule::CamelCase);
+
     let mut fields = vec![];
     for item in &mut ast.items {
         if let syn::TraitItem::Method(m) = item {
-            match TraitMethod::parse(m) {
+            match TraitMethod::parse(m, &renaming) {
                 Some(TraitMethod::Field(f)) => fields.push(f),
                 Some(TraitMethod::Downcast(d)) => {
                     match implementers.iter_mut().find(|i| i.ty == d.ty) {
@@ -125,7 +112,7 @@ pub fn expand_on_trait(
                                 err_duplicate_downcast(m, external, &impler.ty);
                             } else {
                                 impler.downcast = d.downcast;
-                                impler.context_ty = d.context_ty;
+                                impler.context = d.context;
                             }
                         }
                         None => err_only_implementer_downcast(&m.sig),
@@ -141,35 +128,36 @@ pub fn expand_on_trait(
     if fields.is_empty() {
         ERR.emit_custom(trait_span, "must have at least one field");
     }
-
-    if !all_fields_different(&fields) {
+    if !field::all_different(&fields) {
         ERR.emit_custom(trait_span, "must have a different name for each field");
     }
 
     proc_macro_error::abort_if_dirty();
 
-    let context = meta
+    let context = attr
         .context
-        .as_ref()
-        .map(|c| c.as_ref().clone())
+        .as_deref()
+        .cloned()
         .or_else(|| {
             fields.iter().find_map(|f| {
-                f.arguments
-                    .iter()
-                    .find_map(MethodArgument::context_ty)
-                    .cloned()
+                f.arguments.as_ref().and_then(|f| {
+                    f.iter()
+                        .find_map(field::MethodArgument::context_ty)
+                        .cloned()
+                })
             })
         })
         .or_else(|| {
             implementers
                 .iter()
-                .find_map(|impler| impler.context_ty.as_ref())
+                .find_map(|impler| impler.context.as_ref())
                 .cloned()
-        });
+        })
+        .unwrap_or_else(|| parse_quote! { () });
 
-    let is_trait_object = meta.r#dyn.is_some();
+    let is_trait_object = attr.r#dyn.is_some();
 
-    let is_async_trait = meta.asyncness.is_some()
+    let is_async_trait = attr.asyncness.is_some()
         || ast
             .items
             .iter()
@@ -186,14 +174,14 @@ pub fn expand_on_trait(
     let ty = if is_trait_object {
         Type::TraitObject(Box::new(TraitObjectType::new(
             &ast,
-            &meta,
+            &attr,
             scalar.clone(),
             context.clone(),
         )))
     } else {
         Type::Enum(Box::new(EnumType::new(
             &ast,
-            &meta,
+            &attr,
             &implementers,
             scalar.clone(),
         )))
@@ -203,7 +191,7 @@ pub fn expand_on_trait(
         ty,
 
         name,
-        description: meta.description.map(SpanContainer::into_inner),
+        description: attr.description.map(SpanContainer::into_inner),
 
         context,
         scalar: scalar.clone(),
@@ -253,19 +241,15 @@ pub fn expand_on_trait(
 
     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)?;
+/// Expands `#[graphql_interface]` macro placed on a trait implementation block.
+fn expand_on_impl(attrs: Vec<syn::Attribute>, mut ast: syn::ItemImpl) -> syn::Result<TokenStream> {
+    let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?;
 
-    let is_async_trait = meta.asyncness.is_some()
+    let is_async_trait = attr.asyncness.is_some()
         || ast
             .items
             .iter()
@@ -275,30 +259,10 @@ pub fn expand_on_impl(
             })
             .is_some();
 
-    let is_trait_object = meta.r#dyn.is_some();
+    let is_trait_object = attr.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);
+        let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
 
         ast.attrs.push(parse_quote! {
             #[allow(unused_qualifications, clippy::type_repetition_in_bounds)]
@@ -348,7 +312,7 @@ enum TraitMethod {
     /// Method represents a [`Field`] of [GraphQL interface][1].
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
-    Field(Field),
+    Field(field::Definition),
 
     /// Method represents a custom downcasting function into the [`Implementer`] of
     /// [GraphQL interface][1].
@@ -363,7 +327,7 @@ impl TraitMethod {
     /// Returns [`None`] if the trait method marked with `#[graphql(ignore)]` attribute,
     /// or parsing fails.
     #[must_use]
-    fn parse(method: &mut syn::TraitItemMethod) -> Option<Self> {
+    fn parse(method: &mut syn::TraitItemMethod, renaming: &RenameRule) -> Option<Self> {
         let method_attrs = method.attrs.clone();
 
         // Remove repeated attributes from the method, to omit incorrect expansion.
@@ -372,19 +336,19 @@ impl TraitMethod {
             .filter(|attr| !path_eq_single(&attr.path, "graphql"))
             .collect();
 
-        let meta = MethodMeta::from_attrs("graphql", &method_attrs)
+        let attr = field::Attr::from_attrs("graphql", &method_attrs)
             .map_err(|e| proc_macro_error::emit_error!(e))
             .ok()?;
 
-        if meta.ignore.is_some() {
+        if attr.ignore.is_some() {
             return None;
         }
 
-        if meta.downcast.is_some() {
+        if attr.downcast.is_some() {
             return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?)));
         }
 
-        Some(Self::Field(Self::parse_field(method, meta)?))
+        Some(Self::Field(Self::parse_field(method, attr, renaming)?))
     }
 
     /// Parses [`TraitMethod::Downcast`] from the given trait method definition.
@@ -426,8 +390,8 @@ impl TraitMethod {
         Some(Implementer {
             ty,
             downcast: Some(downcast),
-            context_ty,
-            scalar: ScalarValueType::ImplicitGeneric,
+            context: context_ty,
+            scalar: scalar::Type::ImplicitGeneric(None),
         })
     }
 
@@ -435,17 +399,21 @@ impl TraitMethod {
     ///
     /// Returns [`None`] if parsing fails.
     #[must_use]
-    fn parse_field(method: &mut syn::TraitItemMethod, meta: MethodMeta) -> Option<Field> {
+    fn parse_field(
+        method: &mut syn::TraitItemMethod,
+        attr: field::Attr,
+        renaming: &RenameRule,
+    ) -> Option<field::Definition> {
         let method_ident = &method.sig.ident;
 
-        let name = meta
+        let name = attr
             .name
             .as_ref()
             .map(|m| m.as_ref().value())
-            .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string()));
+            .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string()));
         if name.starts_with("__") {
             ERR.no_double_underscore(
-                meta.name
+                attr.name
                     .as_ref()
                     .map(SpanContainer::span_ident)
                     .unwrap_or_else(|| method_ident.span()),
@@ -476,7 +444,7 @@ impl TraitMethod {
             args_iter
                 .filter_map(|arg| match arg {
                     syn::FnArg::Receiver(_) => None,
-                    syn::FnArg::Typed(arg) => Self::parse_field_argument(arg),
+                    syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR),
                 })
                 .collect()
         };
@@ -487,161 +455,58 @@ impl TraitMethod {
         };
         ty.lifetimes_anonymized();
 
-        let description = meta.description.as_ref().map(|d| d.as_ref().value());
-        let deprecated = meta
+        let description = attr.description.as_ref().map(|d| d.as_ref().value());
+        let deprecated = attr
             .deprecated
-            .as_ref()
-            .map(|d| d.as_ref().as_ref().map(syn::LitStr::value));
+            .as_deref()
+            .map(|d| d.as_ref().map(syn::LitStr::value));
 
-        Some(Field {
+        Some(field::Definition {
             name,
             ty,
             description,
             deprecated,
-            method: method_ident.clone(),
-            arguments,
+            ident: method_ident.clone(),
+            arguments: Some(arguments),
+            has_receiver: method.sig.receiver().is_some(),
             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"))
-            .collect();
-
-        let meta = ArgumentMeta::from_attrs("graphql", &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(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({} = ...)]` is not allowed here",
-            arg,
-        ),
-    )
-    .emit();
-
-    None
-}
-
-/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`.
+/// 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(
+    ERR.emit_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`.
+/// 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(
+    ERR.emit_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`.
+/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given
+/// `span`.
 fn err_only_implementer_downcast<S: Spanned>(span: &S) {
-    ERR.custom(
+    ERR.emit_custom(
         span.span(),
         "downcasting is possible only to interface implementers",
-    )
-    .emit();
+    );
 }
 
-/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and `external`
-/// [`ImplementerDowncast`] function.
+/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and
+/// `external` [`ImplementerDowncast`] function.
 fn err_duplicate_downcast(
     method: &syn::TraitItemMethod,
     external: &ImplementerDowncast,
@@ -655,25 +520,17 @@ fn err_duplicate_downcast(
     ERR.custom(
         method.span(),
         format!(
-            "trait method `{}` conflicts with the external downcast function `{}` declared on the \
-             trait to downcast into the implementer type `{}`",
+            "trait method `{}` conflicts with the external downcast function \
+             `{}` declared on the trait to downcast into the implementer type \
+             `{}`",
             method.sig.ident,
             external.to_token_stream(),
             impler_ty.to_token_stream(),
         ),
     )
     .note(String::from(
-        "use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface \
-         implementers downcasting",
+        "use `#[graphql(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
index 78dfa81af..32d01cfeb 100644
--- a/juniper_codegen/src/graphql_interface/mod.rs
+++ b/juniper_codegen/src/graphql_interface/mod.rs
@@ -4,11 +4,15 @@
 
 pub mod attr;
 
-use std::collections::{HashMap, HashSet};
+use std::{
+    collections::{HashMap, HashSet},
+    convert::TryInto as _,
+};
 
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote, ToTokens, TokenStreamExt as _};
 use syn::{
+    ext::IdentExt as _,
     parse::{Parse, ParseStream},
     parse_quote,
     spanned::Spanned as _,
@@ -17,122 +21,133 @@ use syn::{
 
 use crate::{
     common::{
-        gen,
+        field, gen,
         parse::{
             attr::{err, OptionExt as _},
             GenericsExt as _, ParseBufferExt as _,
         },
-        ScalarValueType,
+        scalar,
     },
-    util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer},
+    util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule},
 };
 
-/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait
-/// definition, when generating code for [GraphQL interface][1] type.
+/// Available arguments behind `#[graphql_interface]` attribute placed on a
+/// trait definition, when generating code for [GraphQL interface][1] type.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
 #[derive(Debug, Default)]
-struct TraitMeta {
+struct TraitAttr {
     /// Explicitly specified name of [GraphQL interface][1] type.
     ///
-    /// If absent, then Rust trait name is used by default.
+    /// If [`None`], 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.
+    /// If [`None`], then Rust doc comment is used as [description][2], if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
     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.
+    /// 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.
+    /// If [`None`], 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.
+    /// 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.
+    /// Effectively makes code generation to use a [trait object][2] as a
+    /// [GraphQL interface][1] type rather than an enum. If [`None`], then enum
+    /// is used by default.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html
     r#dyn: Option<SpanContainer<syn::Ident>>,
 
-    /// Explicitly specified Rust types of [GraphQL objects][2] implementing this
-    /// [GraphQL interface][1] type.
+    /// 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.
+    /// 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`].
+    /// If [`None`], then unit type `()` is assumed as a 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.
+    /// Explicitly specified type (or type parameter with its bounds) of
+    /// [`ScalarValue`] to resolve 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
+    /// If [`None`], then generated code will be generic over any
+    /// [`ScalarValue`] type, which, in turn, requires all [interface][1]
+    /// implementers to be generic over any [`ScalarValue`] type too. That's why
+    /// this type should be specified only if one of the implementers implements
     /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
     ///
     /// [`GraphQLType`]: juniper::GraphQLType
     /// [`ScalarValue`]: juniper::ScalarValue
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
-    scalar: Option<SpanContainer<syn::Type>>,
+    scalar: Option<SpanContainer<scalar::AttrValue>>,
 
-    /// Explicitly specified marker indicating that the Rust trait should be transformed into
-    /// [`async_trait`].
+    /// 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.
+    /// If [`None`], 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.
+    /// 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.
+    /// If [`None`], then macro will downcast to the implementers via enum
+    /// dispatch or dynamic dispatch (if the one is chosen). That's why
+    /// specifying an external resolver function has sense, when some custom
+    /// [interface][1] implementer resolving logic is involved.
     ///
-    /// Once the downcasting function is specified for some [GraphQL object][2] implementer type, it
-    /// cannot be downcast another such function or trait method marked with a
-    /// [`MethodMeta::downcast`] marker.
+    /// 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.
+    /// Explicitly specified [`RenameRule`] for all fields of this
+    /// [GraphQL interface][1] type.
+    ///
+    /// If [`None`] then the default rule will be [`RenameRule::CamelCase`].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
+    rename_fields: Option<SpanContainer<RenameRule>>,
+
+    /// 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();
-
+impl Parse for TraitAttr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = 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
+                    out.name
                         .replace(SpanContainer::new(
                             ident.span(),
                             Some(name.span()),
@@ -143,8 +158,7 @@ impl Parse for TraitMeta {
                 "desc" | "description" => {
                     input.parse::<token::Eq>()?;
                     let desc = input.parse::<syn::LitStr>()?;
-                    output
-                        .description
+                    out.description
                         .replace(SpanContainer::new(
                             ident.span(),
                             Some(desc.span()),
@@ -155,16 +169,14 @@ impl Parse for TraitMeta {
                 "ctx" | "context" | "Context" => {
                     input.parse::<token::Eq>()?;
                     let ctx = input.parse::<syn::Type>()?;
-                    output
-                        .context
+                    out.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
+                    let scl = input.parse::<scalar::AttrValue>()?;
+                    out.scalar
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
@@ -174,7 +186,7 @@ impl Parse for TraitMeta {
                         syn::Type, token::Bracket, token::Comma,
                     >()? {
                         let impler_span = impler.span();
-                        output
+                        out
                             .implementers
                             .replace(SpanContainer::new(ident.span(), Some(impler_span), impler))
                             .none_or_else(|_| err::dup_arg(impler_span))?;
@@ -183,23 +195,20 @@ impl Parse for TraitMeta {
                 "dyn" => {
                     input.parse::<token::Eq>()?;
                     let alias = input.parse::<syn::Ident>()?;
-                    output
-                        .r#dyn
+                    out.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
+                    out.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
+                    out.asyncness
                         .replace(SpanContainer::new(span, Some(span), ident))
                         .none_or_else(|_| err::dup_arg(span))?;
                 }
@@ -209,13 +218,23 @@ impl Parse for TraitMeta {
                     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
+                    out.external_downcasts
                         .insert(ty, dwncst_spanned)
                         .none_or_else(|_| err::dup_arg(dwncst_span))?
                 }
+                "rename_all" => {
+                    input.parse::<token::Eq>()?;
+                    let val = input.parse::<syn::LitStr>()?;
+                    out.rename_fields
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(val.span()),
+                            val.try_into()?,
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?;
+                }
                 "internal" => {
-                    output.is_internal = true;
+                    out.is_internal = true;
                 }
                 name => {
                     return Err(err::unknown_arg(&ident, name));
@@ -223,13 +242,13 @@ impl Parse for TraitMeta {
             }
             input.try_parse::<token::Comma>()?;
         }
-
-        Ok(output)
+        Ok(out)
     }
 }
 
-impl TraitMeta {
-    /// Tries to merge two [`TraitMeta`]s into a single one, reporting about duplicates, if any.
+impl TraitAttr {
+    /// Tries to merge two [`TraitAttr`]s into a single one, reporting about
+    /// duplicates, if any.
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         Ok(Self {
             name: try_merge_opt!(name: self, another),
@@ -243,19 +262,20 @@ impl TraitMeta {
             external_downcasts: try_merge_hashmap!(
                 external_downcasts: self, another => span_joined
             ),
+            rename_fields: try_merge_opt!(rename_fields: self, another),
             is_internal: self.is_internal || another.is_internal,
         })
     }
 
-    /// Parses [`TraitMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait
-    /// definition.
+    /// Parses [`TraitAttr`] 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)
+        let mut attr = 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() {
+        if let Some(as_dyn) = &attr.r#dyn {
+            if attr.r#enum.is_some() {
                 return Err(syn::Error::new(
                     as_dyn.span(),
                     "`dyn` attribute argument is not composable with `enum` attribute argument",
@@ -263,77 +283,76 @@ impl TraitMeta {
             }
         }
 
-        if meta.description.is_none() {
-            meta.description = get_doc_comment(attrs);
+        if attr.description.is_none() {
+            attr.description = get_doc_comment(attrs);
         }
 
-        Ok(meta)
+        Ok(attr)
     }
 }
 
-/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait
-/// implementation block, when generating code for [GraphQL interface][1] type.
+/// Available 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.
+struct ImplAttr {
+    /// Explicitly specified type (or type parameter with its bounds) of
+    /// [`ScalarValue`] to implementing the [GraphQL interface][1] type with.
     ///
-    /// If absent, then generated code will be generic over any [`ScalarValue`] type, which, in
-    /// turn, requires all [interface][1] implementers to be generic over any [`ScalarValue`] type
-    /// too. That's why this type should be specified only if the implementer itself implements
-    /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
+    /// 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>>,
+    scalar: Option<SpanContainer<scalar::AttrValue>>,
 
-    /// Explicitly specified marker indicating that the trait implementation block should be
-    /// transformed with applying [`async_trait`].
+    /// 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.
+    /// 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.
+    /// 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).
+    /// 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();
-
+impl Parse for ImplAttr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = Self::default();
         while !input.is_empty() {
             let ident = input.parse_any_ident()?;
             match ident.to_string().as_str() {
                 "scalar" | "Scalar" | "ScalarValue" => {
                     input.parse::<token::Eq>()?;
-                    let scl = input.parse::<syn::Type>()?;
-                    output
-                        .scalar
+                    let scl = input.parse::<scalar::AttrValue>()?;
+                    out.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
+                    out.r#dyn
                         .replace(SpanContainer::new(span, Some(span), ident))
                         .none_or_else(|_| err::dup_arg(span))?;
                 }
                 "async" => {
                     let span = ident.span();
-                    output
-                        .asyncness
+                    out.asyncness
                         .replace(SpanContainer::new(span, Some(span), ident))
                         .none_or_else(|_| err::dup_arg(span))?;
                 }
@@ -343,13 +362,13 @@ impl Parse for ImplMeta {
             }
             input.try_parse::<token::Comma>()?;
         }
-
-        Ok(output)
+        Ok(out)
     }
 }
 
-impl ImplMeta {
-    /// Tries to merge two [`ImplMeta`]s into a single one, reporting about duplicates, if any.
+impl ImplAttr {
+    /// Tries to merge two [`ImplAttr`]s into a single one, reporting about
+    /// duplicates, if any.
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         Ok(Self {
             scalar: try_merge_opt!(scalar: self, another),
@@ -358,8 +377,8 @@ impl ImplMeta {
         })
     }
 
-    /// Parses [`ImplMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait
-    /// implementation block.
+    /// Parses [`ImplAttr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a trait implementation block.
     pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
         filter_attrs(name, attrs)
             .map(|attr| attr.parse_args())
@@ -367,344 +386,6 @@ impl ImplMeta {
     }
 }
 
-/// Available metadata (arguments) behind `#[graphql]` 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]` 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
@@ -724,28 +405,27 @@ struct Definition {
     /// [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`].
+    /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
+    /// for this [GraphQL interface][1].
     ///
     /// [`GraphQLType`]: juniper::GraphQLType
     /// [`Context`]: juniper::Context
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
-    context: Option<syn::Type>,
+    context: syn::Type,
 
-    /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this
-    /// [GraphQL interface][1].
+    /// [`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,
+    scalar: scalar::Type,
 
-    /// Defined [`Field`]s of this [GraphQL interface][1].
+    /// Defined [GraphQL fields][2] of this [GraphQL interface][1].
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
-    fields: Vec<Field>,
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    fields: Vec<field::Definition>,
 
     /// Defined [`Implementer`]s of this [GraphQL interface][1].
     ///
@@ -753,635 +433,298 @@ struct Definition {
     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(),
-            )
-        }
+impl ToTokens for Definition {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.ty.to_token_stream().to_tokens(into);
+        self.impl_graphql_interface_tokens().to_tokens(into);
+        self.impl_output_type_tokens().to_tokens(into);
+        self.impl_graphql_type_tokens().to_tokens(into);
+        self.impl_graphql_value_tokens().to_tokens(into);
+        self.impl_graphql_value_async_tokens().to_tokens(into);
     }
+}
 
-    /// Returns generated code implementing [`GraphQLType`] trait for this [GraphQL interface][1].
+impl Definition {
+    /// Returns generated code implementing [`GraphQLInterface`] trait for this
+    /// [GraphQL interface][1].
     ///
-    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`GraphQLInterface`]: juniper::GraphQLInterface
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     #[must_use]
-    fn impl_graphql_type_tokens(&self) -> TokenStream {
+    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 (impl_generics, where_clause) = self.ty.impl_generics(false);
         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 impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect();
+        let all_implers_unique = (impler_tys.len() > 1).then(|| {
+            quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }
         });
 
-        let fields_meta = self.fields.iter().map(Field::method_meta_tokens);
-
         quote! {
             #[automatically_derived]
-            impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause
+            impl#impl_generics ::juniper::marker::GraphQLInterface<#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()
+                fn mark() {
+                    #all_implers_unique
+                    #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )*
                 }
             }
         }
     }
 
-    /// Returns generated code implementing [`GraphQLValue`] trait for this [GraphQL interface][1].
+    /// Returns generated code implementing [`marker::IsOutputType`] trait for
+    /// this [GraphQL interface][1].
     ///
-    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [`marker::IsOutputType`]: juniper::marker::IsOutputType
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     #[must_use]
-    fn impl_graphql_value_tokens(&self) -> TokenStream {
+    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 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 (impl_generics, where_clause) = self.ty.impl_generics(false);
         let ty = self.ty.ty_tokens();
-        let trait_ty = self.ty.trait_ty();
 
-        let fields_resolvers = self
+        let fields_marks = 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,
+            .map(|f| f.method_mark_tokens(false, scalar));
 
-    /// 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>,
+        let impler_tys = self.implementers.iter().map(|impler| &impler.ty);
 
-    /// Indicator whether this [GraphQL field][2] should be resolved asynchronously.
-    ///
-    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
-    is_async: bool,
-}
+        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 Field {
-    /// Returns generated code for the [`GraphQLType::meta`] method, which registers this
-    /// [`Field`] in [`Registry`].
+    /// Returns generated code implementing [`GraphQLType`] trait for this
+    /// [GraphQL interface][1].
     ///
-    /// [`GraphQLType::meta`]: juniper::GraphQLType::meta
-    /// [`Registry`]: juniper::Registry
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     #[must_use]
-    fn method_meta_tokens(&self) -> TokenStream {
-        let (name, ty) = (&self.name, &self.ty);
+    fn impl_graphql_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, where_clause) = self.ty.impl_generics(false);
+        let ty = self.ty.ty_tokens();
 
+        let name = &self.name;
         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) }
+        // 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 arguments = self
-            .arguments
-            .iter()
-            .filter_map(MethodArgument::method_meta_tokens);
+        let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None));
 
         quote! {
-            registry.field_convert::<#ty, _, Self::Context>(#name, info)
-                #( #arguments )*
-                #description
-                #deprecated
+            #[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 for the [`GraphQLValue::resolve_field`] method, which resolves this
-    /// [`Field`] synchronously.
-    ///
-    /// Returns [`None`] if this [`Field::is_async`].
+    /// Returns generated code implementing [`GraphQLValue`] trait for this
+    /// [GraphQL interface][1].
     ///
-    /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
+    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     #[must_use]
-    fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option<TokenStream> {
-        if self.is_async {
-            return None;
-        }
+    fn impl_graphql_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+        let context = &self.context;
+
+        let (impl_generics, where_clause) = self.ty.impl_generics(false);
+        let ty = self.ty.ty_tokens();
+        let trait_ty = self.ty.trait_ty();
+
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .filter_map(|f| f.method_resolve_field_tokens(scalar, Some(&trait_ty)));
+        let async_fields_panic = {
+            let names = self
+                .fields
+                .iter()
+                .filter_map(|f| f.is_async.then(|| f.name.as_str()))
+                .collect::<Vec<_>>();
+            (!names.is_empty()).then(|| {
+                field::Definition::method_resolve_field_panic_async_field_tokens(&names, scalar)
+            })
+        };
+        let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
 
-        let (name, ty, method) = (&self.name, &self.ty, &self.method);
+        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 arguments = self
-            .arguments
+        let custom_downcasts = self
+            .implementers
             .iter()
-            .map(MethodArgument::method_resolve_field_tokens);
+            .filter_map(|i| i.method_resolve_into_type_tokens(&trait_ty));
+        let regular_downcast = self.ty.method_resolve_into_type_tokens();
 
-        let resolving_code = gen::sync_resolving_code();
+        quote! {
+            #[allow(deprecated)]
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause
+            {
+                type Context = #context;
+                type TypeInfo = ();
 
-        Some(quote! {
-            #name => {
-                let res: #ty = <Self as #trait_ty>::#method(self #( , #arguments )*);
-                #resolving_code
+                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 for the [`GraphQLValueAsync::resolve_field_async`] method, which
-    /// resolves this [`Field`] asynchronously.
+    /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
+    /// [GraphQL interface][1].
     ///
-    /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async
+    /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
+    /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     #[must_use]
-    fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream {
-        let (name, ty, method) = (&self.name, &self.ty, &self.method);
+    fn impl_graphql_value_async_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
 
-        let arguments = self
-            .arguments
-            .iter()
-            .map(MethodArgument::method_resolve_field_tokens);
+        let (impl_generics, where_clause) = self.ty.impl_generics(true);
+        let ty = self.ty.ty_tokens();
+        let trait_ty = self.ty.trait_ty();
 
-        let mut fut = quote! { <Self as #trait_ty>::#method(self #( , #arguments )*) };
-        if !self.is_async {
-            fut = quote! { ::juniper::futures::future::ready(#fut) };
-        }
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .map(|f| f.method_resolve_field_async_tokens(scalar, Some(&trait_ty)));
+        let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
 
-        let resolving_code = gen::async_resolving_code(Some(ty));
+        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! {
-            #name => {
-                let fut = #fut;
-                #resolving_code
+            #[allow(deprecated, non_snake_case)]
+            #[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
+                }
             }
         }
     }
 }
 
-/// Representation of custom downcast into an [`Implementer`] from a [GraphQL interface][1] type for
-/// code generation.
+/// 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].
+    /// 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.
+        /// Indicator whether the trait method accepts a [`Context`] as its
+        /// second argument.
         ///
         /// [`Context`]: juniper::Context
         with_context: bool,
@@ -1399,30 +742,32 @@ enum ImplementerDowncast {
 /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
 #[derive(Clone, Debug)]
 struct Implementer {
-    /// Rust type that this [GraphQL interface][1] [`Implementer`] is represented by.
+    /// 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.
+    /// 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.
+    /// 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.
+    /// 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>,
+    context: Option<syn::Type>,
 
     /// [`ScalarValue`] parametrization of this [`Implementer`].
     ///
     /// [`ScalarValue`]: juniper::ScalarValue
-    scalar: ScalarValueType,
+    scalar: scalar::Type,
 }
 
 impl Implementer {
@@ -1456,8 +801,9 @@ impl Implementer {
         })
     }
 
-    /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns
-    /// name of the GraphQL type represented by this [`Implementer`].
+    /// 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`].
     ///
@@ -1481,12 +827,13 @@ impl Implementer {
         })
     }
 
-    /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts
-    /// the [GraphQL interface][1] type into this [`Implementer`] synchronously.
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0]
+    /// method, which downcasts the [GraphQL interface][1] type into this
+    /// [`Implementer`] synchronously.
     ///
     /// Returns [`None`] if there is no custom [`Implementer::downcast`].
     ///
-    /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type
+    /// [0]: 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> {
@@ -1507,8 +854,10 @@ impl Implementer {
         })
     }
 
-    /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method,
-    /// which downcasts the [GraphQL interface][1] type into this [`Implementer`] asynchronously.
+    /// 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`].
     ///
@@ -1534,7 +883,8 @@ impl Implementer {
     }
 }
 
-/// Representation of Rust enum implementing [GraphQL interface][1] type for code generation.
+/// Representation of Rust enum implementing [GraphQL interface][1] type for
+/// code generation.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
 struct EnumType {
@@ -1544,54 +894,64 @@ struct EnumType {
     /// [`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`].
+    /// 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`].
+    /// 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`].
+    /// [`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`].
+    /// 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`].
+    /// 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`].
+    /// 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`].
+    /// [`ScalarValue`] parametrization to generate [`GraphQLType`]
+    /// implementation with for this [`EnumType`].
     ///
     /// [`GraphQLType`]: juniper::GraphQLType
     /// [`ScalarValue`]: juniper::ScalarValue
-    scalar: ScalarValueType,
+    scalar: scalar::Type,
+}
+
+impl ToTokens for EnumType {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.type_definition_tokens().to_tokens(into);
+        into.append_all(self.impl_from_tokens());
+        self.impl_trait_tokens().to_tokens(into);
+    }
 }
 
 impl EnumType {
-    /// Constructs new [`EnumType`] out of the given parameters.
+    /// Constructs a new [`EnumType`] out of the given parameters.
     #[must_use]
     fn new(
         r#trait: &syn::ItemTrait,
-        meta: &TraitMeta,
+        meta: &TraitAttr,
         implers: &[Implementer],
-        scalar: ScalarValueType,
+        scalar: scalar::Type,
     ) -> Self {
         Self {
             ident: meta
@@ -1641,8 +1001,8 @@ impl EnumType {
         }
     }
 
-    /// Returns name of a single variant of this [`EnumType`] by the given underlying [`syn::Type`]
-    /// of the variant.
+    /// Returns name of a single variant of this [`EnumType`] by the given
+    /// underlying [`syn::Type`] of the variant.
     #[must_use]
     fn variant_ident(ty: &syn::Type) -> &syn::Ident {
         if let syn::Type::Path(p) = ty {
@@ -1652,15 +1012,15 @@ impl EnumType {
         }
     }
 
-    /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant to hold type
-    /// parameters.
+    /// 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 generate code for dispatching non-exhaustive phantom variant of
+    /// this [`EnumType`] in `match` expressions.
     ///
     /// Returns [`None`] if this [`EnumType`] is exhaustive.
     #[must_use]
@@ -1672,30 +1032,68 @@ impl EnumType {
         }
     }
 
-    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation
-    /// for this [`EnumType`].
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
+    /// similar) implementation of this [`EnumType`].
+    ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
     ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
     /// [`GraphQLType`]: juniper::GraphQLType
     #[must_use]
-    fn impl_generics(&self) -> syn::Generics {
+    fn impl_generics(&self, for_async: bool) -> syn::Generics {
         let mut generics = self.trait_generics.clone();
 
         let scalar = &self.scalar;
-        if self.scalar.is_implicit_generic() {
+        if scalar.is_implicit_generic() {
             generics.params.push(parse_quote! { #scalar });
         }
-        if self.scalar.is_generic() {
+        if scalar.is_generic() {
             generics
                 .make_where_clause()
                 .predicates
                 .push(parse_quote! { #scalar: ::juniper::ScalarValue });
         }
+        if let Some(bound) = scalar.bounds() {
+            generics.make_where_clause().predicates.push(bound);
+        }
+
+        if for_async {
+            let self_ty = if self.trait_generics.lifetimes().next().is_some() {
+                // Modify lifetime names to omit "lifetime name `'a` shadows a
+                // lifetime name that is already in scope" error.
+                let mut generics = self.trait_generics.clone();
+                for lt in generics.lifetimes_mut() {
+                    let ident = lt.lifetime.ident.unraw();
+                    lt.lifetime.ident = format_ident!("__fa__{}", ident);
+                }
+
+                let lifetimes = generics.lifetimes().map(|lt| &lt.lifetime);
+                let ty = &self.ident;
+                let (_, ty_generics, _) = generics.split_for_impl();
+
+                quote! { for<#( #lifetimes ),*> #ty#ty_generics }
+            } else {
+                quote! { Self }
+            };
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #self_ty: Sync });
+
+            if scalar.is_generic() {
+                generics
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote! { #scalar: Send + Sync });
+            }
+        }
 
         generics
     }
 
-    /// Returns full type signature of the original trait describing the [GraphQL interface][1] for
-    /// this [`EnumType`].
+    /// 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]
@@ -1717,8 +1115,8 @@ impl EnumType {
 
     /// 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.
+    /// 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;
@@ -1781,8 +1179,8 @@ impl EnumType {
         }
     }
 
-    /// Returns generated code implementing [`From`] trait for this [`EnumType`] from its
-    /// [`EnumType::variants`].
+    /// 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();
@@ -1801,8 +1199,8 @@ impl EnumType {
         })
     }
 
-    /// Returns generated code implementing the original trait describing the [GraphQL interface][1]
-    /// for this [`EnumType`].
+    /// 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]
@@ -1872,6 +1270,7 @@ impl EnumType {
         });
 
         let mut impl_tokens = quote! {
+            #[allow(deprecated)]
             #[automatically_derived]
             impl#impl_generics #trait_ident#generics for #enum_ty#generics #where_clause {
                 #( #assoc_types )*
@@ -1901,10 +1300,11 @@ impl EnumType {
         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`].
+    /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0]
+    /// method, which returns name of the underlying [`Implementer`] GraphQL
+    /// type contained in this [`EnumType`].
     ///
-    /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name
+    /// [0]: juniper::GraphQLValue::concrete_type_name
     #[must_use]
     fn method_concrete_type_name_tokens(&self) -> TokenStream {
         let scalar = &self.scalar;
@@ -1928,10 +1328,11 @@ impl EnumType {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts
-    /// this [`EnumType`] into its underlying [`Implementer`] type synchronously.
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0]
+    /// method, which downcasts this [`EnumType`] into its underlying
+    /// [`Implementer`] type synchronously.
     ///
-    /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type
+    /// [0]: juniper::GraphQLValue::resolve_into_type
     #[must_use]
     fn method_resolve_into_type_tokens(&self) -> TokenStream {
         let resolving_code = gen::sync_resolving_code();
@@ -1953,8 +1354,10 @@ impl EnumType {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method,
-    /// which downcasts this [`EnumType`] into its underlying [`Implementer`] type asynchronously.
+    /// 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]
@@ -1982,16 +1385,8 @@ impl EnumType {
     }
 }
 
-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.
+/// 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
@@ -2002,39 +1397,38 @@ struct TraitObjectType {
     /// [`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`].
+    /// 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`].
+    /// [`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`] parametrization of this [`TraitObjectType`] to generate
+    /// it with.
     ///
     /// [`ScalarValue`]: juniper::ScalarValue
-    scalar: ScalarValueType,
+    scalar: scalar::Type,
 
     /// 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>,
+    context: syn::Type,
 }
 
 impl TraitObjectType {
-    /// Constructs new [`TraitObjectType`] out of the given parameters.
+    /// Constructs a new [`TraitObjectType`] out of the given parameters.
     #[must_use]
     fn new(
         r#trait: &syn::ItemTrait,
-        meta: &TraitMeta,
-        scalar: ScalarValueType,
-        context: Option<syn::Type>,
+        meta: &TraitAttr,
+        scalar: scalar::Type,
+        context: syn::Type,
     ) -> Self {
         Self {
             ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(),
@@ -2046,12 +1440,16 @@ impl TraitObjectType {
         }
     }
 
-    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation
-    /// for this [`TraitObjectType`].
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
+    /// similar) implementation of this [`TraitObjectType`].
+    ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
     ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
     /// [`GraphQLType`]: juniper::GraphQLType
     #[must_use]
-    fn impl_generics(&self) -> syn::Generics {
+    fn impl_generics(&self, for_async: bool) -> syn::Generics {
         let mut generics = self.trait_generics.clone();
 
         generics.params.push(parse_quote! { '__obj });
@@ -2066,12 +1464,28 @@ impl TraitObjectType {
                 .predicates
                 .push(parse_quote! { #scalar: ::juniper::ScalarValue });
         }
+        if let Some(bound) = scalar.bounds() {
+            generics.make_where_clause().predicates.push(bound);
+        }
+
+        if for_async {
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { Self: Sync });
+            if scalar.is_generic() {
+                generics
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote! { #scalar: Send + Sync });
+            }
+        }
 
         generics
     }
 
-    /// Returns full type signature of the original trait describing the [GraphQL interface][1] for
-    /// this [`TraitObjectType`].
+    /// 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]
@@ -2088,7 +1502,8 @@ impl TraitObjectType {
         parse_quote! { #ty#generics }
     }
 
-    /// Returns generated code of the full type signature of this [`TraitObjectType`].
+    /// Returns generated code of the full type signature of this
+    /// [`TraitObjectType`].
     #[must_use]
     fn ty_tokens(&self) -> TokenStream {
         let ty = &self.trait_ident;
@@ -2102,17 +1517,18 @@ impl TraitObjectType {
         }
         let ty_params = &generics.params;
 
-        let context = self.context.clone().unwrap_or_else(|| parse_quote! { () });
+        let context = &self.context;
 
         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`].
+    /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0]
+    /// method, which returns name of the underlying [`Implementer`] GraphQL
+    /// type contained in this [`TraitObjectType`].
     ///
-    /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name
+    /// [0]: juniper::GraphQLValue::concrete_type_name
     #[must_use]
     fn method_concrete_type_name_tokens(&self) -> TokenStream {
         quote! {
@@ -2120,10 +1536,11 @@ impl TraitObjectType {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts
-    /// this [`TraitObjectType`] into its underlying [`Implementer`] type synchronously.
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0]
+    /// method, which downcasts this [`TraitObjectType`] into its underlying
+    /// [`Implementer`] type synchronously.
     ///
-    /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type
+    /// [0]: juniper::GraphQLValue::resolve_into_type
     #[must_use]
     fn method_resolve_into_type_tokens(&self) -> TokenStream {
         let resolving_code = gen::sync_resolving_code();
@@ -2134,9 +1551,10 @@ impl TraitObjectType {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method,
-    /// which downcasts this [`TraitObjectType`] into its underlying [`Implementer`] type
-    /// asynchronously.
+    /// 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]
@@ -2186,7 +1604,7 @@ impl ToTokens for TraitObjectType {
             ty_params_right = Some(quote! { #params, });
         };
 
-        let context = self.context.clone().unwrap_or_else(|| parse_quote! { () });
+        let context = &self.context;
 
         let dyn_alias = quote! {
             #[automatically_derived]
@@ -2200,8 +1618,8 @@ impl ToTokens for TraitObjectType {
     }
 }
 
-/// Representation of possible Rust types implementing [GraphQL interface][1] type for code
-/// generation.
+/// Representation of possible Rust types implementing [GraphQL interface][1]
+/// type for code generation.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
 enum Type {
@@ -2217,21 +1635,36 @@ enum Type {
     TraitObject(Box<TraitObjectType>),
 }
 
+impl ToTokens for Type {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        match self {
+            Self::Enum(e) => e.to_tokens(into),
+            Self::TraitObject(o) => o.to_tokens(into),
+        }
+    }
+}
+
 impl Type {
-    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation
-    /// for this [`Type`].
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
+    /// similar) implementation of this [`Type`].
     ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
+    ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
     /// [`GraphQLType`]: juniper::GraphQLType
     #[must_use]
-    fn impl_generics(&self) -> syn::Generics {
-        match self {
-            Self::Enum(e) => e.impl_generics(),
-            Self::TraitObject(o) => o.impl_generics(),
-        }
+    fn impl_generics(&self, for_async: bool) -> (TokenStream, Option<syn::WhereClause>) {
+        let generics = match self {
+            Self::Enum(e) => e.impl_generics(for_async),
+            Self::TraitObject(o) => o.impl_generics(for_async),
+        };
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        (quote! { #impl_generics }, where_clause.cloned())
     }
 
-    /// Returns full type signature of the original trait describing the [GraphQL interface][1] for
-    /// this [`Type`].
+    /// 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]
@@ -2251,10 +1684,11 @@ impl Type {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns
-    /// name of the underlying [`Implementer`] GraphQL type contained in this [`Type`].
+    /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0]
+    /// method, which returns name of the underlying [`Implementer`] GraphQL
+    /// type contained in this [`Type`].
     ///
-    /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name
+    /// [0]: juniper::GraphQLValue::concrete_type_name
     #[must_use]
     fn method_concrete_type_name_tokens(&self) -> TokenStream {
         match self {
@@ -2263,10 +1697,11 @@ impl Type {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts
-    /// this [`Type`] into its underlying [`Implementer`] type synchronously.
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0]
+    /// method, which downcasts this [`Type`] into its underlying
+    /// [`Implementer`] type synchronously.
     ///
-    /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type
+    /// [0]: juniper::GraphQLValue::resolve_into_type
     #[must_use]
     fn method_resolve_into_type_tokens(&self) -> TokenStream {
         match self {
@@ -2275,8 +1710,10 @@ impl Type {
         }
     }
 
-    /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method,
-    /// which downcasts this [`Type`] into its underlying [`Implementer`] type asynchronously.
+    /// 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 {
@@ -2287,18 +1724,9 @@ impl Type {
     }
 }
 
-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.
+/// 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>,
diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs
new file mode 100644
index 000000000..34a70c211
--- /dev/null
+++ b/juniper_codegen/src/graphql_object/attr.rs
@@ -0,0 +1,255 @@
+//! Code generation for `#[graphql_object]` macro.
+
+use std::{any::TypeId, marker::PhantomData, mem};
+
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, ToTokens};
+use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
+
+use crate::{
+    common::{
+        field,
+        parse::{self, TypeExt as _},
+        scalar,
+    },
+    result::GraphQLScope,
+    util::{path_eq_single, span_container::SpanContainer, RenameRule},
+};
+
+use super::{Attr, Definition, Query};
+
+/// [`GraphQLScope`] of errors for `#[graphql_object]` macro.
+const ERR: GraphQLScope = GraphQLScope::ObjectAttr;
+
+/// Expands `#[graphql_object]` macro into generated code.
+pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
+    if let Ok(mut ast) = syn::parse2::<syn::ItemImpl>(body) {
+        if ast.trait_.is_none() {
+            let impl_attrs = parse::attr::unite(("graphql_object", &attr_args), &ast.attrs);
+            ast.attrs = parse::attr::strip("graphql_object", ast.attrs);
+            return expand_on_impl::<Query>(Attr::from_attrs("graphql_object", &impl_attrs)?, ast);
+        }
+    }
+
+    Err(syn::Error::new(
+        Span::call_site(),
+        "#[graphql_object] attribute is applicable to non-trait `impl` blocks only",
+    ))
+}
+
+/// Expands `#[graphql_object]` macro placed on an implementation block.
+pub(crate) fn expand_on_impl<Operation>(
+    attr: Attr,
+    mut ast: syn::ItemImpl,
+) -> syn::Result<TokenStream>
+where
+    Definition<Operation>: ToTokens,
+    Operation: 'static,
+{
+    let type_span = ast.self_ty.span();
+    let type_ident = ast.self_ty.topmost_ident().ok_or_else(|| {
+        ERR.custom_error(type_span, "could not determine ident for the `impl` type")
+    })?;
+
+    let name = attr
+        .name
+        .clone()
+        .map(SpanContainer::into_inner)
+        .unwrap_or_else(|| type_ident.unraw().to_string());
+    if !attr.is_internal && name.starts_with("__") {
+        ERR.no_double_underscore(
+            attr.name
+                .as_ref()
+                .map(SpanContainer::span_ident)
+                .unwrap_or_else(|| type_ident.span()),
+        );
+    }
+
+    let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
+
+    proc_macro_error::abort_if_dirty();
+
+    let renaming = attr
+        .rename_fields
+        .as_deref()
+        .copied()
+        .unwrap_or(RenameRule::CamelCase);
+
+    let async_only = TypeId::of::<Operation>() != TypeId::of::<Query>();
+    let fields: Vec<_> = ast
+        .items
+        .iter_mut()
+        .filter_map(|item| {
+            if let syn::ImplItem::Method(m) = item {
+                parse_field(m, async_only, &renaming)
+            } else {
+                None
+            }
+        })
+        .collect();
+
+    proc_macro_error::abort_if_dirty();
+
+    if fields.is_empty() {
+        ERR.emit_custom(type_span, "must have at least one field");
+    }
+    if !field::all_different(&fields) {
+        ERR.emit_custom(type_span, "must have a different name for each field");
+    }
+
+    proc_macro_error::abort_if_dirty();
+
+    let context = attr
+        .context
+        .as_deref()
+        .cloned()
+        .or_else(|| {
+            fields.iter().find_map(|f| {
+                f.arguments.as_ref().and_then(|f| {
+                    f.iter()
+                        .find_map(field::MethodArgument::context_ty)
+                        .cloned()
+                })
+            })
+        })
+        .unwrap_or_else(|| parse_quote! { () });
+
+    let generated_code = Definition::<Operation> {
+        name,
+        ty: ast.self_ty.unparenthesized().clone(),
+        generics: ast.generics.clone(),
+        description: attr.description.map(SpanContainer::into_inner),
+        context,
+        scalar,
+        fields,
+        interfaces: attr
+            .interfaces
+            .iter()
+            .map(|ty| ty.as_ref().clone())
+            .collect(),
+        _operation: PhantomData,
+    };
+
+    Ok(quote! {
+        #ast
+        #generated_code
+    })
+}
+
+/// Parses a [`field::Definition`] from the given Rust [`syn::ImplItemMethod`].
+///
+/// Returns [`None`] if parsing fails, or the method field is ignored.
+#[must_use]
+fn parse_field(
+    method: &mut syn::ImplItemMethod,
+    async_only: bool,
+    renaming: &RenameRule,
+) -> Option<field::Definition> {
+    let method_attrs = method.attrs.clone();
+
+    // Remove repeated attributes from the method, to omit incorrect expansion.
+    method.attrs = mem::take(&mut method.attrs)
+        .into_iter()
+        .filter(|attr| !path_eq_single(&attr.path, "graphql"))
+        .collect();
+
+    let attr = field::Attr::from_attrs("graphql", &method_attrs)
+        .map_err(|e| proc_macro_error::emit_error!(e))
+        .ok()?;
+
+    if attr.ignore.is_some() {
+        return None;
+    }
+
+    if async_only && method.sig.asyncness.is_none() {
+        return err_no_sync_resolvers(&method.sig);
+    }
+
+    let method_ident = &method.sig.ident;
+
+    let name = attr
+        .name
+        .as_ref()
+        .map(|m| m.as_ref().value())
+        .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string()));
+    if name.starts_with("__") {
+        ERR.no_double_underscore(
+            attr.name
+                .as_ref()
+                .map(SpanContainer::span_ident)
+                .unwrap_or_else(|| method_ident.span()),
+        );
+        return None;
+    }
+
+    let arguments = {
+        if let Some(arg) = method.sig.inputs.first() {
+            match arg {
+                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);
+                        }
+                    }
+                }
+            }
+        }
+        method
+            .sig
+            .inputs
+            .iter_mut()
+            .filter_map(|arg| match arg {
+                syn::FnArg::Receiver(_) => None,
+                syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR),
+            })
+            .collect()
+    };
+
+    let mut ty = match &method.sig.output {
+        syn::ReturnType::Default => parse_quote! { () },
+        syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(),
+    };
+    ty.lifetimes_anonymized();
+
+    let description = attr.description.as_ref().map(|d| d.as_ref().value());
+    let deprecated = attr
+        .deprecated
+        .as_deref()
+        .map(|d| d.as_ref().map(syn::LitStr::value));
+
+    Some(field::Definition {
+        name,
+        ty,
+        description,
+        deprecated,
+        ident: method_ident.clone(),
+        arguments: Some(arguments),
+        has_receiver: method.sig.receiver().is_some(),
+        is_async: method.sig.asyncness.is_some(),
+    })
+}
+
+/// Emits "invalid method receiver" [`syn::Error`] pointing to the given `span`.
+#[must_use]
+fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
+    ERR.emit_custom(
+        span.span(),
+        "method should have a shared reference receiver `&self`, or no receiver at all",
+    );
+    None
+}
+
+/// Emits "synchronous resolvers are not supported" [`syn::Error`] pointing to
+/// the given `span`.
+#[must_use]
+fn err_no_sync_resolvers<T, S: Spanned>(span: &S) -> Option<T> {
+    ERR.custom(span.span(), "synchronous resolvers are not supported")
+        .note("Specify that this function is async: `async fn foo()`".into())
+        .emit();
+    None
+}
diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs
new file mode 100644
index 000000000..7cbe961b8
--- /dev/null
+++ b/juniper_codegen/src/graphql_object/derive.rs
@@ -0,0 +1,160 @@
+//! Code generation for `#[derive(GraphQLObject)]` macro.
+
+use std::marker::PhantomData;
+
+use proc_macro2::TokenStream;
+use proc_macro_error::ResultExt as _;
+use quote::ToTokens;
+use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
+
+use crate::{
+    common::{field, parse::TypeExt as _, scalar},
+    result::GraphQLScope,
+    util::{span_container::SpanContainer, RenameRule},
+};
+
+use super::{Attr, Definition, Query};
+
+/// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro.
+const ERR: GraphQLScope = GraphQLScope::ObjectDerive;
+
+/// Expands `#[derive(GraphQLObject)]` macro into generated code.
+pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
+    let ast = syn::parse2::<syn::DeriveInput>(input).unwrap_or_abort();
+
+    match &ast.data {
+        syn::Data::Struct(_) => expand_struct(ast),
+        _ => Err(ERR.custom_error(ast.span(), "can only be derived for structs")),
+    }
+    .map(ToTokens::into_token_stream)
+}
+
+/// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a
+/// Rust struct.
+fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> {
+    let attr = Attr::from_attrs("graphql", &ast.attrs)?;
+
+    let struct_span = ast.span();
+    let struct_ident = ast.ident;
+
+    let (_, struct_generics, _) = ast.generics.split_for_impl();
+    let ty = parse_quote! { #struct_ident#struct_generics };
+
+    let name = attr
+        .name
+        .clone()
+        .map(SpanContainer::into_inner)
+        .unwrap_or_else(|| struct_ident.unraw().to_string());
+    if !attr.is_internal && name.starts_with("__") {
+        ERR.no_double_underscore(
+            attr.name
+                .as_ref()
+                .map(SpanContainer::span_ident)
+                .unwrap_or_else(|| struct_ident.span()),
+        );
+    }
+
+    let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
+
+    proc_macro_error::abort_if_dirty();
+
+    let renaming = attr
+        .rename_fields
+        .as_deref()
+        .copied()
+        .unwrap_or(RenameRule::CamelCase);
+
+    let mut fields = vec![];
+    if let syn::Data::Struct(data) = &ast.data {
+        if let syn::Fields::Named(fs) = &data.fields {
+            fields = fs
+                .named
+                .iter()
+                .filter_map(|f| parse_field(f, &renaming))
+                .collect();
+        } else {
+            ERR.emit_custom(struct_span, "only named fields are allowed");
+        }
+    }
+
+    proc_macro_error::abort_if_dirty();
+
+    if fields.is_empty() {
+        ERR.emit_custom(struct_span, "must have at least one field");
+    }
+    if !field::all_different(&fields) {
+        ERR.emit_custom(struct_span, "must have a different name for each field");
+    }
+
+    proc_macro_error::abort_if_dirty();
+
+    Ok(Definition {
+        name,
+        ty,
+        generics: ast.generics,
+        description: attr.description.map(SpanContainer::into_inner),
+        context: attr
+            .context
+            .map(SpanContainer::into_inner)
+            .unwrap_or_else(|| parse_quote! { () }),
+        scalar,
+        fields,
+        interfaces: attr
+            .interfaces
+            .iter()
+            .map(|ty| ty.as_ref().clone())
+            .collect(),
+        _operation: PhantomData,
+    })
+}
+
+/// Parses a [`field::Definition`] from the given Rust struct [`syn::Field`].
+///
+/// Returns [`None`] if parsing fails, or the struct field is ignored.
+#[must_use]
+fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option<field::Definition> {
+    let attr = field::Attr::from_attrs("graphql", &field.attrs)
+        .map_err(|e| proc_macro_error::emit_error!(e))
+        .ok()?;
+
+    if attr.ignore.is_some() {
+        return None;
+    }
+
+    let field_ident = field.ident.as_ref().unwrap();
+
+    let name = attr
+        .name
+        .as_ref()
+        .map(|m| m.as_ref().value())
+        .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string()));
+    if name.starts_with("__") {
+        ERR.no_double_underscore(
+            attr.name
+                .as_ref()
+                .map(SpanContainer::span_ident)
+                .unwrap_or_else(|| field_ident.span()),
+        );
+        return None;
+    }
+
+    let mut ty = field.ty.unparenthesized().clone();
+    ty.lifetimes_anonymized();
+
+    let description = attr.description.as_ref().map(|d| d.as_ref().value());
+    let deprecated = attr
+        .deprecated
+        .as_deref()
+        .map(|d| d.as_ref().map(syn::LitStr::value));
+
+    Some(field::Definition {
+        name,
+        ty,
+        description,
+        deprecated,
+        ident: field_ident.clone(),
+        arguments: None,
+        has_receiver: false,
+        is_async: false,
+    })
+}
diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs
new file mode 100644
index 000000000..267fde795
--- /dev/null
+++ b/juniper_codegen/src/graphql_object/mod.rs
@@ -0,0 +1,622 @@
+//! Code generation for [GraphQL object][1].
+//!
+//! [1]: https://spec.graphql.org/June2018/#sec-Objects
+
+pub mod attr;
+pub mod derive;
+
+use std::{any::TypeId, collections::HashSet, convert::TryInto as _, marker::PhantomData};
+
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned as _,
+    token,
+};
+
+use crate::{
+    common::{
+        field,
+        parse::{
+            attr::{err, OptionExt as _},
+            ParseBufferExt as _, TypeExt,
+        },
+        scalar,
+    },
+    util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule},
+};
+use syn::ext::IdentExt;
+
+/// Available arguments behind `#[graphql]` (or `#[graphql_object]`) attribute
+/// when generating code for [GraphQL object][1] type.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Objects
+#[derive(Debug, Default)]
+pub(crate) struct Attr {
+    /// Explicitly specified name of this [GraphQL object][1] type.
+    ///
+    /// If [`None`], then Rust type name is used by default.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) name: Option<SpanContainer<String>>,
+
+    /// Explicitly specified [description][2] of this [GraphQL object][1] type.
+    ///
+    /// If [`None`], then Rust doc comment is used as [description][2], if any.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
+    pub(crate) description: Option<SpanContainer<String>>,
+
+    /// Explicitly specified type of [`Context`] to use for resolving this
+    /// [GraphQL object][1] type with.
+    ///
+    /// If [`None`], then unit type `()` is assumed as a type of [`Context`].
+    ///
+    /// [`Context`]: juniper::Context
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) context: Option<SpanContainer<syn::Type>>,
+
+    /// Explicitly specified type (or type parameter with its bounds) of
+    /// [`ScalarValue`] to use for resolving this [GraphQL object][1] type with.
+    ///
+    /// If [`None`], then generated code will be generic over any
+    /// [`ScalarValue`] type, which, in turn, requires all [object][1] fields to
+    /// be generic over any [`ScalarValue`] type too. That's why this type
+    /// should be specified only if one of the variants implements
+    /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) scalar: Option<SpanContainer<scalar::AttrValue>>,
+
+    /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1]
+    /// type implements.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces
+    pub(crate) interfaces: HashSet<SpanContainer<syn::Type>>,
+
+    /// Explicitly specified [`RenameRule`] for all fields of this
+    /// [GraphQL object][1] type.
+    ///
+    /// If [`None`] then the default rule will be [`RenameRule::CamelCase`].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) rename_fields: Option<SpanContainer<RenameRule>>,
+
+    /// Indicator whether the generated code is intended to be used only inside
+    /// the [`juniper`] library.
+    pub(crate) is_internal: bool,
+}
+
+impl Parse for Attr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = 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>()?;
+                    out.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>()?;
+                    out.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>()?;
+                    out.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::<scalar::AttrValue>()?;
+                    out.scalar
+                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
+                "impl" | "implements" | "interfaces" => {
+                    input.parse::<token::Eq>()?;
+                    for iface in input.parse_maybe_wrapped_and_punctuated::<
+                        syn::Type, token::Bracket, token::Comma,
+                    >()? {
+                        let iface_span = iface.span();
+                        out
+                            .interfaces
+                            .replace(SpanContainer::new(ident.span(), Some(iface_span), iface))
+                            .none_or_else(|_| err::dup_arg(iface_span))?;
+                    }
+                }
+                "rename_all" => {
+                    input.parse::<token::Eq>()?;
+                    let val = input.parse::<syn::LitStr>()?;
+                    out.rename_fields
+                        .replace(SpanContainer::new(
+                            ident.span(),
+                            Some(val.span()),
+                            val.try_into()?,
+                        ))
+                        .none_or_else(|_| err::dup_arg(&ident))?;
+                }
+                "internal" => {
+                    out.is_internal = true;
+                }
+                name => {
+                    return Err(err::unknown_arg(&ident, name));
+                }
+            }
+            input.try_parse::<token::Comma>()?;
+        }
+        Ok(out)
+    }
+}
+
+impl Attr {
+    /// Tries to merge two [`Attr`]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),
+            interfaces: try_merge_hashset!(interfaces: self, another => span_joined),
+            rename_fields: try_merge_opt!(rename_fields: self, another),
+            is_internal: self.is_internal || another.is_internal,
+        })
+    }
+
+    /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a struct or impl block definition.
+    pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
+        let mut attr = filter_attrs(name, attrs)
+            .map(|attr| attr.parse_args())
+            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
+
+        if attr.description.is_none() {
+            attr.description = get_doc_comment(attrs);
+        }
+
+        Ok(attr)
+    }
+}
+
+/// Definition of [GraphQL object][1] for code generation.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Objects
+#[derive(Debug)]
+pub(crate) struct Definition<Operation: ?Sized> {
+    /// Name of this [GraphQL object][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) name: String,
+
+    /// Rust type that this [GraphQL object][1] is represented with.
+    ///
+    /// It should contain all its generics, if any.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) ty: syn::Type,
+
+    /// Generics of the Rust type that this [GraphQL object][1] is implemented
+    /// for.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) generics: syn::Generics,
+
+    /// Description of this [GraphQL object][1] to put into GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) description: Option<String>,
+
+    /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
+    /// for this [GraphQL object][1].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`Context`]: juniper::Context
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) context: syn::Type,
+
+    /// [`ScalarValue`] parametrization to generate [`GraphQLType`]
+    /// implementation with for this [GraphQL object][1].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    pub(crate) scalar: scalar::Type,
+
+    /// Defined [GraphQL fields][2] of this [GraphQL object][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields
+    pub(crate) fields: Vec<field::Definition>,
+
+    /// [GraphQL interfaces][2] implemented by this [GraphQL object][1].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces
+    pub(crate) interfaces: HashSet<syn::Type>,
+
+    /// [GraphQL operation][1] this [`Definition`] should generate code for.
+    ///
+    /// Either [GraphQL query][2] or [GraphQL subscription][3].
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Language.Operations
+    /// [2]: https://spec.graphql.org/June2018/#sec-Query
+    /// [3]: https://spec.graphql.org/June2018/#sec-Subscription
+    pub(crate) _operation: PhantomData<Box<Operation>>,
+}
+
+impl<Operation: ?Sized + 'static> Definition<Operation> {
+    /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`]
+    /// trait (and similar) implementation of this [GraphQL object][1].
+    ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
+    ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    pub(crate) fn impl_generics(&self, for_async: bool) -> (TokenStream, Option<syn::WhereClause>) {
+        let mut generics = self.generics.clone();
+
+        let scalar = &self.scalar;
+        if scalar.is_implicit_generic() {
+            generics.params.push(parse_quote! { #scalar });
+        }
+        if scalar.is_generic() {
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
+        }
+        if let Some(bound) = scalar.bounds() {
+            generics.make_where_clause().predicates.push(bound);
+        }
+
+        if for_async {
+            let self_ty = if self.generics.lifetimes().next().is_some() {
+                let mut lifetimes = vec![];
+
+                // Modify lifetime names to omit "lifetime name `'a` shadows a
+                // lifetime name that is already in scope" error.
+                let mut ty = self.ty.clone();
+                ty.lifetimes_iter_mut(&mut |lt| {
+                    let ident = lt.ident.unraw();
+                    lt.ident = format_ident!("__fa__{}", ident);
+                    lifetimes.push(lt.clone());
+                });
+
+                quote! { for<#( #lifetimes ),*> #ty }
+            } else {
+                quote! { Self }
+            };
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #self_ty: Sync });
+
+            if scalar.is_generic() {
+                generics
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote! { #scalar: Send + Sync });
+            }
+        }
+
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        (quote! { #impl_generics }, where_clause.cloned())
+    }
+
+    /// Returns generated code implementing [`marker::IsOutputType`] trait for
+    /// this [GraphQL object][1].
+    ///
+    /// [`marker::IsOutputType`]: juniper::marker::IsOutputType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    pub(crate) fn impl_output_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, where_clause) = self.impl_generics(false);
+        let ty = &self.ty;
+
+        let coerce_result = TypeId::of::<Operation>() != TypeId::of::<Query>();
+        let fields_marks = self
+            .fields
+            .iter()
+            .map(|f| f.method_mark_tokens(coerce_result, scalar));
+
+        let interface_tys = self.interfaces.iter();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause
+            {
+                fn mark() {
+                    #( #fields_marks )*
+                    #( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLType`] trait for this
+    /// [GraphQL object][1].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    pub(crate) fn impl_graphql_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, where_clause) = self.impl_generics(false);
+        let ty = &self.ty;
+
+        let name = &self.name;
+        let description = self
+            .description
+            .as_ref()
+            .map(|desc| quote! { .description(#desc) });
+
+        let extract_stream_type = TypeId::of::<Operation>() != TypeId::of::<Query>();
+        let fields_meta = self
+            .fields
+            .iter()
+            .map(|f| f.method_meta_tokens(extract_stream_type.then(|| scalar)));
+
+        // Sorting is required to preserve/guarantee the order of interfaces registered in schema.
+        let mut interface_tys: Vec<_> = self.interfaces.iter().collect();
+        interface_tys.sort_unstable_by(|a, b| {
+            let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string());
+            a.cmp(&b)
+        });
+        let interfaces = (!interface_tys.is_empty()).then(|| {
+            quote! {
+                .interfaces(&[
+                    #( registry.get_type::<#interface_tys>(info), )*
+                ])
+            }
+        });
+
+        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,
+                {
+                    let fields = [
+                        #( #fields_meta, )*
+                    ];
+                    registry.build_object_type::<#ty>(info, &fields)
+                        #description
+                        #interfaces
+                        .into_meta()
+                }
+            }
+        }
+    }
+}
+
+/// [GraphQL query operation][2] of the [`Definition`] to generate code for.
+///
+/// [2]: https://spec.graphql.org/June2018/#sec-Query
+struct Query;
+
+impl ToTokens for Definition<Query> {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.impl_graphql_object_tokens().to_tokens(into);
+        self.impl_output_type_tokens().to_tokens(into);
+        self.impl_graphql_type_tokens().to_tokens(into);
+        self.impl_graphql_value_tokens().to_tokens(into);
+        self.impl_graphql_value_async_tokens().to_tokens(into);
+        self.impl_as_dyn_graphql_value_tokens().to_tokens(into);
+    }
+}
+
+impl Definition<Query> {
+    /// Returns generated code implementing [`GraphQLObject`] trait for this
+    /// [GraphQL object][1].
+    ///
+    /// [`GraphQLObject`]: juniper::GraphQLObject
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    fn impl_graphql_object_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, where_clause) = self.impl_generics(false);
+        let ty = &self.ty;
+
+        let interface_tys = self.interfaces.iter();
+        // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion,
+        //       but considering generics.
+        //let interface_tys: Vec<_> = self.interfaces.iter().collect();
+        //let all_interfaces_unique = (interface_tys.len() > 1).then(|| {
+        //    quote! { ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); }
+        //});
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #where_clause
+            {
+                fn mark() {
+                    #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )*
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLValue`] trait for this
+    /// [GraphQL object][1].
+    ///
+    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    fn impl_graphql_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+        let context = &self.context;
+
+        let (impl_generics, where_clause) = self.impl_generics(false);
+        let ty = &self.ty;
+
+        let name = &self.name;
+
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .filter_map(|f| f.method_resolve_field_tokens(scalar, None));
+        let async_fields_panic = {
+            let names = self
+                .fields
+                .iter()
+                .filter_map(|f| f.is_async.then(|| f.name.as_str()))
+                .collect::<Vec<_>>();
+            (!names.is_empty()).then(|| {
+                field::Definition::method_resolve_field_panic_async_field_tokens(&names, scalar)
+            })
+        };
+        let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
+
+        quote! {
+            #[allow(deprecated)]
+            #[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,
+                    _: &Self::Context,
+                    _: &Self::TypeInfo,
+                ) -> String {
+                    #name.to_string()
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
+    /// [GraphQL object][1].
+    ///
+    /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    fn impl_graphql_value_async_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, where_clause) = self.impl_generics(true);
+        let ty = &self.ty;
+
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .map(|f| f.method_resolve_field_async_tokens(scalar, None));
+        let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
+
+        quote! {
+            #[allow(deprecated, non_snake_case)]
+            #[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,
+                    }
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`AsDynGraphQLValue`] trait for this
+    /// [GraphQL object][1].
+    ///
+    /// [`AsDynGraphQLValue`]: juniper::AsDynGraphQLValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Objects
+    #[must_use]
+    fn impl_as_dyn_graphql_value_tokens(&self) -> Option<TokenStream> {
+        if self.interfaces.is_empty() {
+            return None;
+        }
+
+        let scalar = &self.scalar;
+
+        let (impl_generics, where_clause) = self.impl_generics(true);
+        let ty = &self.ty;
+
+        Some(quote! {
+            #[allow(non_snake_case)]
+            #[automatically_derived]
+            impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #where_clause
+            {
+                type Context = <Self as ::juniper::GraphQLValue<#scalar>>::Context;
+                type TypeInfo = <Self as ::juniper::GraphQLValue<#scalar>>::TypeInfo;
+
+                fn as_dyn_graphql_value(
+                    &self,
+                ) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> {
+                    self
+                }
+
+                fn as_dyn_graphql_value_async(
+                    &self,
+                ) -> &::juniper::DynGraphQLValueAsync<#scalar, Self::Context, Self::TypeInfo> {
+                    self
+                }
+            }
+        })
+    }
+}
diff --git a/juniper_codegen/src/graphql_subscription/attr.rs b/juniper_codegen/src/graphql_subscription/attr.rs
new file mode 100644
index 000000000..288baca0a
--- /dev/null
+++ b/juniper_codegen/src/graphql_subscription/attr.rs
@@ -0,0 +1,29 @@
+//! Code generation for `#[graphql_subscription]` macro.
+
+use proc_macro2::{Span, TokenStream};
+
+use crate::{
+    common::parse,
+    graphql_object::{attr::expand_on_impl, Attr},
+};
+
+use super::Subscription;
+
+/// Expands `#[graphql_subscription]` macro into generated code.
+pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
+    if let Ok(mut ast) = syn::parse2::<syn::ItemImpl>(body) {
+        if ast.trait_.is_none() {
+            let impl_attrs = parse::attr::unite(("graphql_subscription", &attr_args), &ast.attrs);
+            ast.attrs = parse::attr::strip("graphql_subscription", ast.attrs);
+            return expand_on_impl::<Subscription>(
+                Attr::from_attrs("graphql_subscription", &impl_attrs)?,
+                ast,
+            );
+        }
+    }
+
+    Err(syn::Error::new(
+        Span::call_site(),
+        "#[graphql_subscription] attribute is applicable to non-trait `impl` blocks only",
+    ))
+}
diff --git a/juniper_codegen/src/graphql_subscription/mod.rs b/juniper_codegen/src/graphql_subscription/mod.rs
new file mode 100644
index 000000000..8f6b0e06f
--- /dev/null
+++ b/juniper_codegen/src/graphql_subscription/mod.rs
@@ -0,0 +1,139 @@
+//! Code generation for [GraphQL subscription][1].
+//!
+//! [1]: https://spec.graphql.org/June2018/#sec-Subscription
+
+pub mod attr;
+
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::parse_quote;
+
+use crate::{common::field, graphql_object::Definition};
+
+/// [GraphQL subscription operation][2] of the [`Definition`] to generate code
+/// for.
+///
+/// [2]: https://spec.graphql.org/June2018/#sec-Subscription
+struct Subscription;
+
+impl ToTokens for Definition<Subscription> {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.impl_output_type_tokens().to_tokens(into);
+        self.impl_graphql_type_tokens().to_tokens(into);
+        self.impl_graphql_value_tokens().to_tokens(into);
+        self.impl_graphql_subscription_value_tokens()
+            .to_tokens(into);
+    }
+}
+
+impl Definition<Subscription> {
+    /// Returns generated code implementing [`GraphQLValue`] trait for this
+    /// [GraphQL subscription][1].
+    ///
+    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Subscription
+    #[must_use]
+    fn impl_graphql_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+        let context = &self.context;
+
+        let (impl_generics, where_clause) = self.impl_generics(false);
+        let ty = &self.ty;
+
+        let name = &self.name;
+
+        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,
+                    _: &Self::TypeInfo,
+                    _: &str,
+                    _: &::juniper::Arguments<#scalar>,
+                    _: &::juniper::Executor<Self::Context, #scalar>,
+                ) -> ::juniper::ExecutionResult<#scalar> {
+                    panic!("Called `resolve_field` on subscription object");
+                }
+
+                fn concrete_type_name(
+                    &self,
+                    _: &Self::Context,
+                    _: &Self::TypeInfo,
+                ) -> String {
+                    #name.to_string()
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLSubscriptionValue`] trait
+    /// for this [GraphQL subscription][1].
+    ///
+    /// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Subscription
+    #[must_use]
+    fn impl_graphql_subscription_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        // We use `for_async = false` here as `GraphQLSubscriptionValue` requires
+        // simpler and less `Send`/`Sync` bounds than `GraphQLValueAsync`.
+        let (impl_generics, mut where_clause) = self.impl_generics(false);
+        if scalar.is_generic() {
+            where_clause = Some(where_clause.unwrap_or_else(|| parse_quote! { where }));
+            where_clause
+                .as_mut()
+                .unwrap()
+                .predicates
+                .push(parse_quote! { #scalar: Send + Sync });
+        }
+        let ty = &self.ty;
+
+        let fields_resolvers = self
+            .fields
+            .iter()
+            .map(|f| f.method_resolve_field_into_stream_tokens(scalar));
+        let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar);
+
+        quote! {
+            #[allow(deprecated)]
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #where_clause
+            {
+                fn resolve_field_into_stream<
+                    's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f,
+                >(
+                    &'s self,
+                    info: &'i Self::TypeInfo,
+                    field: &'fi str,
+                    args: ::juniper::Arguments<'args, #scalar>,
+                    executor: &'ref_e ::juniper::Executor<'ref_e, 'e, Self::Context, #scalar>,
+                ) -> ::juniper::BoxFuture<'f, std::result::Result<
+                    ::juniper::Value<::juniper::ValuesStream<'res, #scalar>>,
+                    ::juniper::FieldError<#scalar>,
+                >>
+                where
+                    's: 'f,
+                    'fi: 'f,
+                    'args: 'f,
+                    'ref_e: 'f,
+                    'res: 'f,
+                    'i: 'res,
+                    'e: 'res,
+                {
+                    match field {
+                        #( #fields_resolvers )*
+                        _ => #no_field_panic,
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs
index c2617d1d2..ae2be4db4 100644
--- a/juniper_codegen/src/graphql_union/attr.rs
+++ b/juniper_codegen/src/graphql_union/attr.rs
@@ -7,14 +7,14 @@ use quote::{quote, ToTokens as _};
 use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
 
 use crate::{
-    common::parse,
+    common::{parse, scalar},
     result::GraphQLScope,
     util::{path_eq_single, span_container::SpanContainer},
 };
 
 use super::{
-    all_variants_different, emerge_union_variants_from_meta, UnionDefinition, UnionMeta,
-    UnionVariantDefinition, UnionVariantMeta,
+    all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr,
+    VariantDefinition,
 };
 
 /// [`GraphQLScope`] of errors for `#[graphql_union]` macro.
@@ -22,28 +22,36 @@ const ERR: GraphQLScope = GraphQLScope::UnionAttr;
 
 /// Expands `#[graphql_union]` macro into generated code.
 pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
-    let mut ast = syn::parse2::<syn::ItemTrait>(body).map_err(|_| {
-        syn::Error::new(
-            Span::call_site(),
-            "#[graphql_union] attribute is applicable to trait definitions only",
-        )
-    })?;
-    let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs);
-    ast.attrs = parse::attr::strip("graphql_union", ast.attrs);
+    if let Ok(mut ast) = syn::parse2::<syn::ItemTrait>(body) {
+        let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs);
+        ast.attrs = parse::attr::strip("graphql_union", ast.attrs);
+        return expand_on_trait(trait_attrs, ast);
+    }
+
+    Err(syn::Error::new(
+        Span::call_site(),
+        "#[graphql_union] attribute is applicable to trait definitions only",
+    ))
+}
 
-    let meta = UnionMeta::from_attrs("graphql_union", &trait_attrs)?;
+/// Expands `#[graphql_union]` macro placed on a trait definition.
+fn expand_on_trait(
+    attrs: Vec<syn::Attribute>,
+    mut ast: syn::ItemTrait,
+) -> syn::Result<TokenStream> {
+    let attr = Attr::from_attrs("graphql_union", &attrs)?;
 
     let trait_span = ast.span();
     let trait_ident = &ast.ident;
 
-    let name = meta
+    let name = attr
         .name
         .clone()
         .map(SpanContainer::into_inner)
         .unwrap_or_else(|| trait_ident.unraw().to_string());
-    if !meta.is_internal && name.starts_with("__") {
+    if !attr.is_internal && name.starts_with("__") {
         ERR.no_double_underscore(
-            meta.name
+            attr.name
                 .as_ref()
                 .map(SpanContainer::span_ident)
                 .unwrap_or_else(|| trait_ident.span()),
@@ -54,14 +62,14 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
         .items
         .iter_mut()
         .filter_map(|i| match i {
-            syn::TraitItem::Method(m) => parse_variant_from_trait_method(m, trait_ident, &meta),
+            syn::TraitItem::Method(m) => parse_variant_from_trait_method(m, trait_ident, &attr),
             _ => None,
         })
         .collect();
 
     proc_macro_error::abort_if_dirty();
 
-    emerge_union_variants_from_meta(&mut variants, meta.external_resolvers);
+    emerge_union_variants_from_attr(&mut variants, attr.external_resolvers);
 
     if variants.is_empty() {
         ERR.emit_custom(trait_span, "expects at least one union variant");
@@ -76,40 +84,40 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
 
     proc_macro_error::abort_if_dirty();
 
-    let context = meta
+    let context = attr
         .context
         .map(SpanContainer::into_inner)
-        .or_else(|| variants.iter().find_map(|v| v.context_ty.as_ref()).cloned());
+        .or_else(|| variants.iter().find_map(|v| v.context.as_ref()).cloned())
+        .unwrap_or_else(|| parse_quote! { () });
 
-    let generated_code = UnionDefinition {
+    let generated_code = Definition {
         name,
         ty: parse_quote! { #trait_ident },
         is_trait_object: true,
-        description: meta.description.map(SpanContainer::into_inner),
+        description: attr.description.map(SpanContainer::into_inner),
         context,
-        scalar: meta.scalar.map(SpanContainer::into_inner),
+        scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
         generics: ast.generics.clone(),
         variants,
     };
 
     Ok(quote! {
         #ast
-
         #generated_code
     })
 }
 
 /// Parses given Rust trait `method` as [GraphQL union][1] variant.
 ///
-/// On failure returns [`None`] and internally fills up [`proc_macro_error`] with the corresponding
-/// errors.
+/// On failure returns [`None`] and internally fills up [`proc_macro_error`]
+/// with the corresponding errors.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
 fn parse_variant_from_trait_method(
     method: &mut syn::TraitItemMethod,
     trait_ident: &syn::Ident,
-    trait_meta: &UnionMeta,
-) -> Option<UnionVariantDefinition> {
+    trait_attr: &Attr,
+) -> Option<VariantDefinition> {
     let method_attrs = method.attrs.clone();
 
     // Remove repeated attributes from the method, to omit incorrect expansion.
@@ -118,22 +126,22 @@ fn parse_variant_from_trait_method(
         .filter(|attr| !path_eq_single(&attr.path, "graphql"))
         .collect();
 
-    let meta = UnionVariantMeta::from_attrs("graphql", &method_attrs)
+    let attr = VariantAttr::from_attrs("graphql", &method_attrs)
         .map_err(|e| proc_macro_error::emit_error!(e))
         .ok()?;
 
-    if let Some(rslvr) = meta.external_resolver {
+    if let Some(rslvr) = attr.external_resolver {
         ERR.custom(
             rslvr.span_ident(),
             "cannot use #[graphql(with = ...)] attribute on a trait method",
         )
         .note(String::from(
-            "instead use #[graphql(ignore)] on the method with #[graphql_union(on ... = ...)] on \
-             the trait itself",
+            "instead use #[graphql(ignore)] on the method with \
+             #[graphql_union(on ... = ...)] on the trait itself",
         ))
         .emit()
     }
-    if meta.ignore.is_some() {
+    if attr.ignore.is_some() {
         return None;
     }
 
@@ -165,21 +173,21 @@ fn parse_variant_from_trait_method(
     }
 
     let resolver_code = {
-        if let Some(other) = trait_meta.external_resolvers.get(&ty) {
+        if let Some(other) = trait_attr.external_resolvers.get(&ty) {
             ERR.custom(
                 method_span,
                 format!(
-                    "trait method `{}` conflicts with the external resolver function `{}` declared \
-                     on the trait to resolve the variant type `{}`",
+                    "trait method `{}` conflicts with the external resolver \
+                     function `{}` declared on the trait to resolve the \
+                     variant type `{}`",
                     method_ident,
                     other.to_token_stream(),
                     ty.to_token_stream(),
-
                 ),
             )
             .note(String::from(
-                "use `#[graphql(ignore)]` attribute to ignore this trait method for union \
-                 variants resolution",
+                "use `#[graphql(ignore)]` attribute to ignore this trait \
+                 method for union variants resolution",
             ))
             .emit();
         }
@@ -195,17 +203,18 @@ fn parse_variant_from_trait_method(
         }
     };
 
-    // 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.
+    // 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.
     let resolver_check = parse_quote! {
         ({ #resolver_code } as ::std::option::Option<&#ty>).is_some()
     };
 
-    Some(UnionVariantDefinition {
+    Some(VariantDefinition {
         ty,
         resolver_code,
         resolver_check,
-        context_ty: method_context_ty,
+        context: method_context_ty,
     })
 }
diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs
index 161ddf6a9..1c1bc5c1d 100644
--- a/juniper_codegen/src/graphql_union/derive.rs
+++ b/juniper_codegen/src/graphql_union/derive.rs
@@ -6,12 +6,14 @@ use quote::{quote, ToTokens};
 use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields};
 
 use crate::{
-    common::parse::TypeExt as _, result::GraphQLScope, util::span_container::SpanContainer,
+    common::{parse::TypeExt as _, scalar},
+    result::GraphQLScope,
+    util::span_container::SpanContainer,
 };
 
 use super::{
-    all_variants_different, emerge_union_variants_from_meta, UnionDefinition, UnionMeta,
-    UnionVariantDefinition, UnionVariantMeta,
+    all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr,
+    VariantDefinition,
 };
 
 /// [`GraphQLScope`] of errors for `#[derive(GraphQLUnion)]` macro.
@@ -29,21 +31,22 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
     .map(ToTokens::into_token_stream)
 }
 
-/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a Rust enum.
-fn expand_enum(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
-    let meta = UnionMeta::from_attrs("graphql", &ast.attrs)?;
+/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a
+/// Rust enum.
+fn expand_enum(ast: syn::DeriveInput) -> syn::Result<Definition> {
+    let attr = Attr::from_attrs("graphql", &ast.attrs)?;
 
     let enum_span = ast.span();
     let enum_ident = ast.ident;
 
-    let name = meta
+    let name = attr
         .name
         .clone()
         .map(SpanContainer::into_inner)
         .unwrap_or_else(|| enum_ident.unraw().to_string());
-    if !meta.is_internal && name.starts_with("__") {
+    if !attr.is_internal && name.starts_with("__") {
         ERR.no_double_underscore(
-            meta.name
+            attr.name
                 .as_ref()
                 .map(SpanContainer::span_ident)
                 .unwrap_or_else(|| enum_ident.span()),
@@ -55,12 +58,12 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
         _ => unreachable!(),
     }
     .into_iter()
-    .filter_map(|var| parse_variant_from_enum_variant(var, &enum_ident, &meta))
+    .filter_map(|var| parse_variant_from_enum_variant(var, &enum_ident, &attr))
     .collect();
 
     proc_macro_error::abort_if_dirty();
 
-    emerge_union_variants_from_meta(&mut variants, meta.external_resolvers);
+    emerge_union_variants_from_attr(&mut variants, attr.external_resolvers);
 
     if variants.is_empty() {
         ERR.emit_custom(enum_span, "expects at least one union variant");
@@ -75,13 +78,16 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
 
     proc_macro_error::abort_if_dirty();
 
-    Ok(UnionDefinition {
+    Ok(Definition {
         name,
         ty: parse_quote! { #enum_ident },
         is_trait_object: false,
-        description: meta.description.map(SpanContainer::into_inner),
-        context: meta.context.map(SpanContainer::into_inner),
-        scalar: meta.scalar.map(SpanContainer::into_inner),
+        description: attr.description.map(SpanContainer::into_inner),
+        context: attr
+            .context
+            .map(SpanContainer::into_inner)
+            .unwrap_or_else(|| parse_quote! { () }),
+        scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
         generics: ast.generics,
         variants,
     })
@@ -89,19 +95,19 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
 
 /// Parses given Rust enum `var`iant as [GraphQL union][1] variant.
 ///
-/// On failure returns [`None`] and internally fills up [`proc_macro_error`] with the corresponding
-/// errors.
+/// On failure returns [`None`] and internally fills up [`proc_macro_error`]
+/// with the corresponding errors.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
 fn parse_variant_from_enum_variant(
     var: syn::Variant,
     enum_ident: &syn::Ident,
-    enum_meta: &UnionMeta,
-) -> Option<UnionVariantDefinition> {
-    let meta = UnionVariantMeta::from_attrs("graphql", &var.attrs)
+    enum_attr: &Attr,
+) -> Option<VariantDefinition> {
+    let attr = VariantAttr::from_attrs("graphql", &var.attrs)
         .map_err(|e| proc_macro_error::emit_error!(e))
         .ok()?;
-    if meta.ignore.is_some() {
+    if attr.ignore.is_some() {
         return None;
     }
 
@@ -129,12 +135,13 @@ fn parse_variant_from_enum_variant(
 
     let enum_path = quote! { #enum_ident::#var_ident };
 
-    let resolver_code = if let Some(rslvr) = meta.external_resolver {
-        if let Some(other) = enum_meta.external_resolvers.get(&ty) {
+    let resolver_code = if let Some(rslvr) = attr.external_resolver {
+        if let Some(other) = enum_attr.external_resolvers.get(&ty) {
             ERR.emit_custom(
                 rslvr.span_ident(),
                 format!(
-                    "variant `{}` already has external resolver function `{}` declared on the enum",
+                    "variant `{}` already has external resolver function `{}` \
+                     declared on the enum",
                     ty.to_token_stream(),
                     other.to_token_stream(),
                 ),
@@ -156,29 +163,30 @@ fn parse_variant_from_enum_variant(
         matches!(self, #enum_path(_))
     };
 
-    Some(UnionVariantDefinition {
+    Some(VariantDefinition {
         ty,
         resolver_code,
         resolver_check,
-        context_ty: None,
+        context: None,
     })
 }
 
-/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a Rust struct.
-fn expand_struct(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
-    let meta = UnionMeta::from_attrs("graphql", &ast.attrs)?;
+/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a
+/// Rust struct.
+fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition> {
+    let attr = Attr::from_attrs("graphql", &ast.attrs)?;
 
     let struct_span = ast.span();
     let struct_ident = ast.ident;
 
-    let name = meta
+    let name = attr
         .name
         .clone()
         .map(SpanContainer::into_inner)
         .unwrap_or_else(|| struct_ident.unraw().to_string());
-    if !meta.is_internal && name.starts_with("__") {
+    if !attr.is_internal && name.starts_with("__") {
         ERR.no_double_underscore(
-            meta.name
+            attr.name
                 .as_ref()
                 .map(SpanContainer::span_ident)
                 .unwrap_or_else(|| struct_ident.span()),
@@ -186,7 +194,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
     }
 
     let mut variants = vec![];
-    emerge_union_variants_from_meta(&mut variants, meta.external_resolvers);
+    emerge_union_variants_from_attr(&mut variants, attr.external_resolvers);
     if variants.is_empty() {
         ERR.emit_custom(struct_span, "expects at least one union variant");
     }
@@ -200,13 +208,16 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
 
     proc_macro_error::abort_if_dirty();
 
-    Ok(UnionDefinition {
+    Ok(Definition {
         name,
         ty: parse_quote! { #struct_ident },
         is_trait_object: false,
-        description: meta.description.map(SpanContainer::into_inner),
-        context: meta.context.map(SpanContainer::into_inner),
-        scalar: meta.scalar.map(SpanContainer::into_inner),
+        description: attr.description.map(SpanContainer::into_inner),
+        context: attr
+            .context
+            .map(SpanContainer::into_inner)
+            .unwrap_or_else(|| parse_quote! { () }),
+        scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics),
         generics: ast.generics,
         variants,
     })
diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs
index d34541708..c882c24ba 100644
--- a/juniper_codegen/src/graphql_union/mod.rs
+++ b/juniper_codegen/src/graphql_union/mod.rs
@@ -8,8 +8,9 @@ pub mod derive;
 use std::collections::HashMap;
 
 use proc_macro2::TokenStream;
-use quote::{quote, ToTokens, TokenStreamExt as _};
+use quote::{format_ident, quote, ToTokens};
 use syn::{
+    ext::IdentExt as _,
     parse::{Parse, ParseStream},
     parse_quote,
     spanned::Spanned as _,
@@ -17,83 +18,90 @@ use syn::{
 };
 
 use crate::{
-    common::parse::{
-        attr::{err, OptionExt as _},
-        ParseBufferExt as _,
+    common::{
+        gen,
+        parse::{
+            attr::{err, OptionExt as _},
+            ParseBufferExt as _,
+        },
+        scalar,
     },
     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>>;
+/// Helper alias for the type of [`Attr::external_resolvers`] field.
+type AttrResolvers = HashMap<syn::Type, SpanContainer<syn::ExprPath>>;
 
-/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_union]`) attribute when
-/// generating code for [GraphQL union][1] type.
+/// Available arguments behind `#[graphql]` (or `#[graphql_union]`) attribute
+/// when generating code for [GraphQL union][1] type.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
 #[derive(Debug, Default)]
-struct UnionMeta {
+struct Attr {
     /// Explicitly specified name of [GraphQL union][1] type.
     ///
-    /// If absent, then Rust type name is used by default.
+    /// If [`None`], then Rust type name is used by default.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub name: Option<SpanContainer<String>>,
+    name: Option<SpanContainer<String>>,
 
     /// Explicitly specified [description][2] of [GraphQL union][1] type.
     ///
-    /// If absent, then Rust doc comment is used as [description][2], if any.
+    /// If [`None`], then Rust doc comment is used as [description][2], if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
     /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
-    pub description: Option<SpanContainer<String>>,
+    description: Option<SpanContainer<String>>,
 
-    /// Explicitly specified type of `juniper::Context` to use for resolving this [GraphQL union][1]
-    /// type with.
+    /// Explicitly specified type of [`Context`] to use for resolving this
+    /// [GraphQL union][1] type with.
     ///
-    /// If absent, then unit type `()` is assumed as type of `juniper::Context`.
+    /// If [`None`], then unit type `()` is assumed as a type of [`Context`].
     ///
+    /// [`Context`]: juniper::Context
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub context: Option<SpanContainer<syn::Type>>,
+    context: Option<SpanContainer<syn::Type>>,
 
-    /// Explicitly specified type of `juniper::ScalarValue` to use for resolving this
+    /// Explicitly specified type of [`ScalarValue`] to use for resolving this
     /// [GraphQL union][1] type with.
     ///
-    /// If absent, then generated code will be generic over any `juniper::ScalarValue` type, which,
-    /// in turn, requires all [union][1] variants to be generic over any `juniper::ScalarValue` type
-    /// too. That's why this type should be specified only if one of the variants implements
-    /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` type.
+    /// If [`None`], then generated code will be generic over any
+    /// [`ScalarValue`] type, which, in turn, requires all [union][1] variants
+    /// to be generic over any [`ScalarValue`] type too. That's why this type
+    /// should be specified only if one of the variants implements
+    /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
     ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub scalar: Option<SpanContainer<syn::Type>>,
+    scalar: Option<SpanContainer<scalar::AttrValue>>,
 
-    /// Explicitly specified external resolver functions for [GraphQL union][1] variants.
+    /// Explicitly specified external resolver functions for [GraphQL union][1]
+    /// variants.
     ///
-    /// If absent, then macro will try to auto-infer all the possible variants from the type
-    /// declaration, if possible. That's why specifying an external resolver function has sense,
-    /// when some custom [union][1] variant resolving logic is involved, or variants cannot be
-    /// inferred.
+    /// If [`None`], then macro will try to auto-infer all the possible variants
+    /// from the type declaration, if possible. That's why specifying an
+    /// external resolver function has sense, when some custom [union][1]
+    /// variant resolving logic is involved, or variants cannot be inferred.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub external_resolvers: UnionMetaResolvers,
+    external_resolvers: AttrResolvers,
 
-    /// Indicator whether the generated code is intended to be used only inside the `juniper`
-    /// library.
-    pub is_internal: bool,
+    /// Indicator whether the generated code is intended to be used only inside
+    /// the [`juniper`] library.
+    is_internal: bool,
 }
 
-impl Parse for UnionMeta {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let mut output = Self::default();
-
+impl Parse for Attr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = 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
+                    out.name
                         .replace(SpanContainer::new(
                             ident.span(),
                             Some(name.span()),
@@ -104,8 +112,7 @@ impl Parse for UnionMeta {
                 "desc" | "description" => {
                     input.parse::<token::Eq>()?;
                     let desc = input.parse::<syn::LitStr>()?;
-                    output
-                        .description
+                    out.description
                         .replace(SpanContainer::new(
                             ident.span(),
                             Some(desc.span()),
@@ -116,16 +123,14 @@ impl Parse for UnionMeta {
                 "ctx" | "context" | "Context" => {
                     input.parse::<token::Eq>()?;
                     let ctx = input.parse::<syn::Type>()?;
-                    output
-                        .context
+                    out.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
+                    let scl = input.parse::<scalar::AttrValue>()?;
+                    out.scalar
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
@@ -135,13 +140,12 @@ impl Parse for UnionMeta {
                     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
+                    out.external_resolvers
                         .insert(ty, rslvr_spanned)
                         .none_or_else(|_| err::dup_arg(rslvr_span))?
                 }
                 "internal" => {
-                    output.is_internal = true;
+                    out.is_internal = true;
                 }
                 name => {
                     return Err(err::unknown_arg(&ident, name));
@@ -149,13 +153,13 @@ impl Parse for UnionMeta {
             }
             input.try_parse::<token::Comma>()?;
         }
-
-        Ok(output)
+        Ok(out)
     }
 }
 
-impl UnionMeta {
-    /// Tries to merge two [`UnionMeta`]s into a single one, reporting about duplicates, if any.
+impl Attr {
+    /// Tries to merge two [`Attr`]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),
@@ -169,9 +173,9 @@ impl UnionMeta {
         })
     }
 
-    /// 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> {
+    /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
+    /// placed on a type 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?))?;
@@ -184,17 +188,17 @@ impl UnionMeta {
     }
 }
 
-/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_union]`) attribute when
-/// generating code for [GraphQL union][1]'s variant.
+/// Available arguments behind `#[graphql]` attribute when generating code for
+/// [GraphQL union][1]'s variant.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
 #[derive(Debug, Default)]
-struct UnionVariantMeta {
-    /// Explicitly specified marker for the variant/field being ignored and not included into
-    /// [GraphQL union][1].
+struct VariantAttr {
+    /// Explicitly specified marker for the variant/field being ignored and not
+    /// included into [GraphQL union][1].
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub ignore: Option<SpanContainer<syn::Ident>>,
+    ignore: Option<SpanContainer<syn::Ident>>,
 
     /// Explicitly specified external resolver function for this [GraphQL union][1] variant.
     ///
@@ -203,25 +207,23 @@ struct UnionVariantMeta {
     /// logic is involved.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub external_resolver: Option<SpanContainer<syn::ExprPath>>,
+    external_resolver: Option<SpanContainer<syn::ExprPath>>,
 }
 
-impl Parse for UnionVariantMeta {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let mut output = Self::default();
-
+impl Parse for VariantAttr {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut out = Self::default();
         while !input.is_empty() {
             let ident = input.parse::<syn::Ident>()?;
             match ident.to_string().as_str() {
-                "ignore" | "skip" => output
+                "ignore" | "skip" => out
                     .ignore
                     .replace(SpanContainer::new(ident.span(), None, ident.clone()))
                     .none_or_else(|_| err::dup_arg(&ident))?,
                 "with" => {
                     input.parse::<token::Eq>()?;
                     let rslvr = input.parse::<syn::ExprPath>()?;
-                    output
-                        .external_resolver
+                    out.external_resolver
                         .replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
@@ -231,14 +233,13 @@ impl Parse for UnionVariantMeta {
             }
             input.try_parse::<token::Comma>()?;
         }
-
-        Ok(output)
+        Ok(out)
     }
 }
 
-impl UnionVariantMeta {
-    /// Tries to merge two [`UnionVariantMeta`]s into a single one, reporting about duplicates, if
-    /// any.
+impl VariantAttr {
+    /// Tries to merge two [`VariantAttr`]s into a single one, reporting about
+    /// duplicates, if any.
     fn try_merge(self, mut another: Self) -> syn::Result<Self> {
         Ok(Self {
             ignore: try_merge_opt!(ignore: self, another),
@@ -246,219 +247,238 @@ impl UnionVariantMeta {
         })
     }
 
-    /// 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> {
+    /// Parses [`VariantAttr`] from the given multiple `name`d
+    /// [`syn::Attribute`]s placed on a variant/field/method definition.
+    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?))
     }
 }
 
-/// Definition of [GraphQL union][1] variant for code generation.
-///
-/// [1]: https://spec.graphql.org/June2018/#sec-Unions
-struct UnionVariantDefinition {
-    /// Rust type that this [GraphQL union][1] variant resolves into.
-    ///
-    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub ty: syn::Type,
-
-    /// Rust code for value resolution of this [GraphQL union][1] variant.
-    ///
-    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub resolver_code: syn::Expr,
-
-    /// Rust code for checking whether [GraphQL union][1] should be resolved into this variant.
-    ///
-    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub resolver_check: syn::Expr,
-
-    /// Rust type of `juniper::Context` that this [GraphQL union][1] variant requires for
-    /// resolution.
-    ///
-    /// It's available only when code generation happens for Rust traits and a trait method contains
-    /// context argument.
-    ///
-    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub context_ty: Option<syn::Type>,
-}
-
 /// Definition of [GraphQL union][1] for code generation.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-struct UnionDefinition {
+struct Definition {
     /// Name of this [GraphQL union][1] in GraphQL schema.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub name: String,
+    name: String,
 
     /// Rust type that this [GraphQL union][1] is represented with.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub ty: syn::Type,
+    ty: syn::Type,
 
-    /// Generics of the Rust type that this [GraphQL union][1] is implemented for.
+    /// 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,
+    generics: syn::Generics,
 
-    /// Indicator whether code should be generated for a trait object, rather than for a regular
-    /// Rust type.
-    pub is_trait_object: bool,
+    /// Indicator whether code should be generated for a trait object, rather
+    /// than for a regular Rust type.
+    is_trait_object: bool,
 
     /// Description of this [GraphQL union][1] to put into GraphQL schema.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub description: Option<String>,
+    description: Option<String>,
 
-    /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` implementation with
+    /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
     /// for this [GraphQL union][1].
     ///
-    /// If [`None`] then generated code will use unit type `()` as `juniper::Context`.
-    ///
+    /// [`Context`]: juniper::Context
+    /// [`GraphQLType`]: juniper::GraphQLType
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub context: Option<syn::Type>,
+    context: syn::Type,
 
-    /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` implementation with
-    /// for this [GraphQL union][1].
+    /// Rust type of [`ScalarValue`] to generate [`GraphQLType`] implementation
+    /// with for this [GraphQL union][1].
     ///
-    /// If [`None`] then generated code will be generic over any `juniper::ScalarValue` type, which,
-    /// in turn, requires all [union][1] variants to be generic over any `juniper::ScalarValue` type
-    /// too. That's why this type should be specified only if one of the variants implements
-    /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` type.
+    /// If [`None`] then generated code will be generic over any [`ScalarValue`]
+    /// type, which, in turn, requires all [union][1] variants to be generic
+    /// over any [`ScalarValue`] type too. That's why this type should be
+    /// specified only if one of the variants implements [`GraphQLType`] in a
+    /// non-generic way over [`ScalarValue`] type.
     ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub scalar: Option<syn::Type>,
+    scalar: scalar::Type,
 
     /// Variants definitions of this [GraphQL union][1].
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-    pub variants: Vec<UnionVariantDefinition>,
+    variants: Vec<VariantDefinition>,
 }
 
-impl ToTokens for UnionDefinition {
+impl ToTokens for Definition {
     fn to_tokens(&self, into: &mut TokenStream) {
-        let name = &self.name;
-        let ty = &self.ty;
+        self.impl_graphql_union_tokens().to_tokens(into);
+        self.impl_output_type_tokens().to_tokens(into);
+        self.impl_graphql_type_tokens().to_tokens(into);
+        self.impl_graphql_value_tokens().to_tokens(into);
+        self.impl_graphql_value_async_tokens().to_tokens(into);
+    }
+}
 
-        let context = self
-            .context
-            .as_ref()
-            .map(|ctx| quote! { #ctx })
-            .unwrap_or_else(|| quote! { () });
+impl Definition {
+    /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`]
+    /// trait (and similar) implementation of this [GraphQL union][1].
+    ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
+    ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    #[must_use]
+    fn impl_generics(
+        &self,
+        for_async: bool,
+    ) -> (TokenStream, TokenStream, Option<syn::WhereClause>) {
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+        let ty = &self.ty;
 
-        let scalar = self
-            .scalar
-            .as_ref()
-            .map(|scl| quote! { #scl })
-            .unwrap_or_else(|| quote! { __S });
+        let mut ty_full = quote! { #ty#ty_generics };
+        if self.is_trait_object {
+            ty_full = quote! { dyn #ty_full + '__obj + Send + Sync };
+        }
 
-        let description = self
-            .description
-            .as_ref()
-            .map(|desc| quote! { .description(#desc) });
+        let mut generics = self.generics.clone();
 
-        let var_types: Vec<_> = self.variants.iter().map(|var| &var.ty).collect();
+        if self.is_trait_object {
+            generics.params.push(parse_quote! { '__obj });
+        }
 
-        let all_variants_unique = if var_types.len() > 1 {
-            Some(quote! { ::juniper::sa::assert_type_ne_all!(#(#var_types),*); })
-        } else {
-            None
-        };
+        let scalar = &self.scalar;
+        if scalar.is_implicit_generic() {
+            generics.params.push(parse_quote! { #scalar });
+        }
+        if scalar.is_generic() {
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
+        }
+        if let Some(bound) = scalar.bounds() {
+            generics.make_where_clause().predicates.push(bound);
+        }
 
-        let match_names = self.variants.iter().map(|var| {
-            let var_ty = &var.ty;
-            let var_check = &var.resolver_check;
-            quote! {
-                if #var_check {
-                    return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)
-                        .unwrap().to_string();
+        if for_async {
+            let self_ty = if !self.is_trait_object && self.generics.lifetimes().next().is_some() {
+                // Modify lifetime names to omit "lifetime name `'a` shadows a
+                // lifetime name that is already in scope" error.
+                let mut generics = self.generics.clone();
+                for lt in generics.lifetimes_mut() {
+                    let ident = lt.lifetime.ident.unraw();
+                    lt.lifetime.ident = format_ident!("__fa__{}", ident);
                 }
+
+                let lifetimes = generics.lifetimes().map(|lt| &lt.lifetime);
+                let ty = &self.ty;
+                let (_, ty_generics, _) = generics.split_for_impl();
+
+                quote! { for<#( #lifetimes ),*> #ty#ty_generics }
+            } else {
+                quote! { Self }
+            };
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #self_ty: Sync });
+
+            if scalar.is_generic() {
+                generics
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote! { #scalar: Send + Sync });
             }
+        }
+
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        (
+            quote! { #impl_generics },
+            quote! { #ty_full },
+            where_clause.cloned(),
+        )
+    }
+
+    /// Returns generated code implementing [`GraphQLUnion`] trait for this
+    /// [GraphQL union][1].
+    ///
+    /// [`GraphQLUnion`]: juniper::GraphQLUnion
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    #[must_use]
+    fn impl_graphql_union_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);
+
+        let variant_tys: Vec<_> = self.variants.iter().map(|var| &var.ty).collect();
+        let all_variants_unique = (variant_tys.len() > 1).then(|| {
+            quote! { ::juniper::sa::assert_type_ne_all!(#( #variant_tys ),*); }
         });
 
-        let match_resolves: Vec<_> = self.variants.iter().map(|var| &var.resolver_code).collect();
-        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(info)) };
-            quote! {
-                if type_name == #get_name.unwrap() {
-                    return ::juniper::IntoResolvable::into(
-                        { #expr },
-                        executor.context(),
-                    )
-                    .and_then(|res| match res {
-                        Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r),
-                        None => Ok(::juniper::Value::null()),
-                    });
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full #where_clause
+            {
+                fn mark() {
+                    #all_variants_unique
+                    #( <#variant_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )*
                 }
             }
-        });
-        let resolve_into_type_async =
-            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(info))
-                    };
-                    quote! {
-                        if type_name == #get_name.unwrap() {
-                            let res = ::juniper::IntoResolvable::into(
-                                { #expr },
-                                executor.context(),
-                            );
-                            return Box::pin(async move {
-                                match res? {
-                                    Some((ctx, r)) => {
-                                        let subexec = executor.replaced_context(ctx);
-                                        subexec.resolve_with_ctx_async(info, &r).await
-                                    },
-                                    None => Ok(::juniper::Value::null()),
-                                }
-                            });
-                        }
-                    }
-                });
+        }
+    }
 
-        let (_, ty_generics, _) = self.generics.split_for_impl();
+    /// Returns generated code implementing [`marker::IsOutputType`] trait for
+    /// this [GraphQL union][1].
+    ///
+    /// [`marker::IsOutputType`]: juniper::marker::IsOutputType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    #[must_use]
+    fn impl_output_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
 
-        let mut ext_generics = self.generics.clone();
-        if self.is_trait_object {
-            ext_generics.params.push(parse_quote! { '__obj });
-        }
-        if self.scalar.is_none() {
-            ext_generics.params.push(parse_quote! { #scalar });
-            ext_generics
-                .make_where_clause()
-                .predicates
-                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
-        }
-        let (ext_impl_generics, _, where_clause) = ext_generics.split_for_impl();
-
-        let mut where_async = where_clause
-            .cloned()
-            .unwrap_or_else(|| parse_quote! { where });
-        where_async.predicates.push(parse_quote! { Self: Sync });
-        if self.scalar.is_none() {
-            where_async
-                .predicates
-                .push(parse_quote! { #scalar: Send + Sync });
-        }
+        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);
 
-        let mut ty_full = quote! { #ty#ty_generics };
-        if self.is_trait_object {
-            ty_full = quote! { dyn #ty_full + '__obj + Send + Sync };
+        let variant_tys = self.variants.iter().map(|var| &var.ty);
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full #where_clause
+            {
+                fn mark() {
+                    #( <#variant_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
+                }
+            }
         }
+    }
+
+    /// Returns generated code implementing [`GraphQLType`] trait for this
+    /// [GraphQL union][1].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    #[must_use]
+    fn impl_graphql_type_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);
 
-        let type_impl = quote! {
+        let name = &self.name;
+        let description = self
+            .description
+            .as_ref()
+            .map(|desc| quote! { .description(#desc) });
+
+        let variant_tys = self.variants.iter().map(|var| &var.ty);
+
+        quote! {
             #[automatically_derived]
-            impl#ext_impl_generics ::juniper::GraphQLType<#scalar> for #ty_full
-                #where_clause
+            impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty_full #where_clause
             {
                 fn name(_ : &Self::TypeInfo) -> Option<&'static str> {
                     Some(#name)
@@ -471,19 +491,43 @@ impl ToTokens for UnionDefinition {
                 where #scalar: 'r,
                 {
                     let types = [
-                        #( registry.get_type::<#var_types>(info), )*
+                        #( registry.get_type::<#variant_tys>(info), )*
                     ];
                     registry.build_union_type::<#ty_full>(info, &types)
-                    #description
-                    .into_meta()
+                        #description
+                        .into_meta()
                 }
             }
-        };
+        }
+    }
 
-        let value_impl = quote! {
+    /// Returns generated code implementing [`GraphQLValue`] trait for this
+    /// [GraphQL union][1].
+    ///
+    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    #[must_use]
+    fn impl_graphql_value_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+        let context = &self.context;
+
+        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);
+
+        let name = &self.name;
+
+        let match_variant_names = self
+            .variants
+            .iter()
+            .map(|v| v.method_concrete_type_name_tokens(scalar));
+
+        let variant_resolvers = self
+            .variants
+            .iter()
+            .map(|v| v.method_resolve_into_type_tokens(scalar));
+
+        quote! {
             #[automatically_derived]
-            impl#ext_impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full
-                #where_clause
+            impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full #where_clause
             {
                 type Context = #context;
                 type TypeInfo = ();
@@ -497,10 +541,10 @@ impl ToTokens for UnionDefinition {
                     context: &Self::Context,
                     info: &Self::TypeInfo,
                 ) -> String {
-                    #( #match_names )*
+                    #( #match_variant_names )*
                     panic!(
-                        "GraphQL union {} cannot be resolved into any of its variants in its \
-                         current state",
+                        "GraphQL union `{}` cannot be resolved into any of its \
+                         variants in its current state",
                         #name,
                     );
                 }
@@ -513,19 +557,39 @@ impl ToTokens for UnionDefinition {
                     executor: &::juniper::Executor<Self::Context, #scalar>,
                 ) -> ::juniper::ExecutionResult<#scalar> {
                     let context = executor.context();
-                    #( #resolve_into_type )*
+                    #( #variant_resolvers )*
                     panic!(
-                        "Concrete type {} is not handled by instance resolvers on GraphQL union {}",
+                        "Concrete type `{}` is not handled by instance \
+                         resolvers on GraphQL union `{}`",
                         type_name, #name,
                     );
                 }
             }
-        };
+        }
+    }
 
-        let value_async_impl = quote! {
+    /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
+    /// [GraphQL union][1].
+    ///
+    /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    #[must_use]
+    fn impl_graphql_value_async_tokens(&self) -> TokenStream {
+        let scalar = &self.scalar;
+
+        let (impl_generics, ty_full, where_clause) = self.impl_generics(true);
+
+        let name = &self.name;
+
+        let variant_async_resolvers = self
+            .variants
+            .iter()
+            .map(|v| v.method_resolve_into_type_async_tokens(scalar));
+
+        quote! {
+            #[allow(non_snake_case)]
             #[automatically_derived]
-            impl#ext_impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full
-                #where_async
+            impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_clause
             {
                 fn resolve_into_type_async<'b>(
                     &'b self,
@@ -535,58 +599,119 @@ impl ToTokens for UnionDefinition {
                     executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar>
                 ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
                     let context = executor.context();
-                    #( #resolve_into_type_async )*
+                    #( #variant_async_resolvers )*
                     panic!(
-                        "Concrete type {} is not handled by instance resolvers on GraphQL union {}",
+                        "Concrete type `{}` is not handled by instance \
+                         resolvers on GraphQL union `{}`",
                         type_name, #name,
                     );
                 }
             }
-        };
+        }
+    }
+}
 
-        let output_type_impl = quote! {
-            #[automatically_derived]
-            impl#ext_impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full
-                #where_clause
-            {
-                fn mark() {
-                    #( <#var_types as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
-                }
+/// Definition of [GraphQL union][1] variant for code generation.
+///
+/// [1]: https://spec.graphql.org/June2018/#sec-Unions
+struct VariantDefinition {
+    /// Rust type that this [GraphQL union][1] variant resolves into.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    ty: syn::Type,
+
+    /// Rust code for value resolution of this [GraphQL union][1] variant.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    resolver_code: syn::Expr,
+
+    /// Rust code for checking whether [GraphQL union][1] should be resolved
+    /// into this variant.
+    ///
+    /// [1]: https://spec.graphql.org/June2018/#sec-Unions
+    resolver_check: syn::Expr,
+
+    /// Rust type of [`Context`] that this [GraphQL union][1] variant requires
+    /// for resolution.
+    ///
+    /// 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-Unions
+    context: Option<syn::Type>,
+}
+
+impl VariantDefinition {
+    /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0]
+    /// method, which returns name of the underlying GraphQL type contained in
+    /// this [`VariantDefinition`].
+    ///
+    /// [0]: juniper::GraphQLValue::concrete_type_name
+    #[must_use]
+    fn method_concrete_type_name_tokens(&self, scalar: &scalar::Type) -> TokenStream {
+        let ty = &self.ty;
+        let check = &self.resolver_check;
+
+        quote! {
+            if #check {
+                return <#ty as ::juniper::GraphQLType<#scalar>>::name(info)
+                    .unwrap()
+                    .to_string();
             }
-        };
+        }
+    }
 
-        let union_impl = quote! {
-            #[automatically_derived]
-            impl#ext_impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full
-                #where_clause
-            {
-                fn mark() {
-                    #all_variants_unique
+    /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0]
+    /// method, which resolves the underlying GraphQL type contained in this
+    /// [`VariantDefinition`] synchronously.
+    ///
+    /// [0]: juniper::GraphQLValue::resolve_into_type
+    #[must_use]
+    fn method_resolve_into_type_tokens(&self, scalar: &scalar::Type) -> TokenStream {
+        let ty = &self.ty;
+        let expr = &self.resolver_code;
+        let resolving_code = gen::sync_resolving_code();
 
-                    #( <#var_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )*
-                }
+        quote! {
+            if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
+                let res = { #expr };
+                return #resolving_code;
             }
-        };
+        }
+    }
 
-        into.append_all(&[
-            union_impl,
-            output_type_impl,
-            type_impl,
-            value_impl,
-            value_async_impl,
-        ]);
+    /// Returns generated code for the
+    /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which
+    /// resolves the underlying GraphQL type contained in this
+    /// [`VariantDefinition`] asynchronously.
+    ///
+    /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async
+    #[must_use]
+    fn method_resolve_into_type_async_tokens(&self, scalar: &scalar::Type) -> TokenStream {
+        let ty = &self.ty;
+        let expr = &self.resolver_code;
+        let resolving_code = gen::async_resolving_code(None);
+
+        quote! {
+            if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() {
+                let fut = ::juniper::futures::future::ready({ #expr });
+                return #resolving_code;
+            }
+        }
     }
 }
 
-/// Emerges [`UnionMeta::external_resolvers`] into the given [GraphQL union][1] `variants`.
+/// Emerges [`Attr::external_resolvers`] into the given [GraphQL union][1]
+/// `variants`.
 ///
 /// If duplication happens, then resolving code is overwritten with the one from
 /// `external_resolvers`.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-fn emerge_union_variants_from_meta(
-    variants: &mut Vec<UnionVariantDefinition>,
-    external_resolvers: UnionMetaResolvers,
+fn emerge_union_variants_from_attr(
+    variants: &mut Vec<VariantDefinition>,
+    external_resolvers: AttrResolvers,
 ) {
     if external_resolvers.is_empty() {
         return;
@@ -597,9 +722,10 @@ fn emerge_union_variants_from_meta(
         let resolver_code = parse_quote! {
             #resolver_fn(self, ::juniper::FromContext::from(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.
+        // 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.
         let resolver_check = parse_quote! {
             ({ #resolver_code } as ::std::option::Option<&#ty>).is_some()
         };
@@ -608,29 +734,30 @@ fn emerge_union_variants_from_meta(
             var.resolver_code = resolver_code;
             var.resolver_check = resolver_check;
         } else {
-            variants.push(UnionVariantDefinition {
+            variants.push(VariantDefinition {
                 ty,
                 resolver_code,
                 resolver_check,
-                context_ty: None,
+                context: None,
             })
         }
     }
 }
 
-/// Checks whether all [GraphQL union][1] `variants` represent a different Rust type.
+/// Checks whether all [GraphQL union][1] `variants` represent a different Rust
+/// type.
 ///
 /// # Notice
 ///
-/// This is not an optimal implementation, as it's possible to bypass this check by using a full
-/// qualified path instead (`crate::Test` vs `Test`). Since this requirement is mandatory, the
-/// static assertion [`assert_type_ne_all!`][2] is used to enforce this requirement in the generated
-/// code. However, due to the bad error message this implementation should stay and provide
-/// guidance.
+/// This is not an optimal implementation, as it's possible to bypass this check
+/// by using a full qualified path instead (`crate::Test` vs `Test`). Since this
+/// requirement is mandatory, the static assertion [`assert_type_ne_all!`][2] is
+/// used to enforce this requirement in the generated code. However, due to the
+/// bad error message this implementation should stay and provide guidance.
 ///
 /// [1]: https://spec.graphql.org/June2018/#sec-Unions
-/// [2]: https://docs.rs/static_assertions/latest/static_assertions/macro.assert_type_ne_all.html
-fn all_variants_different(variants: &[UnionVariantDefinition]) -> bool {
+/// [2]: juniper::sa::assert_type_ne_all
+fn all_variants_different(variants: &[VariantDefinition]) -> bool {
     let mut types: Vec<_> = variants.iter().map(|var| &var.ty).collect();
     types.dedup();
     types.len() == variants.len()
diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs
deleted file mode 100644
index 9de0a7551..000000000
--- a/juniper_codegen/src/impl_object.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-#![allow(clippy::collapsible_if)]
-
-use crate::{
-    result::{GraphQLScope, UnsupportedAttribute},
-    util::{self, span_container::SpanContainer, RenameRule},
-};
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{ext::IdentExt, spanned::Spanned};
-
-/// Generate code for the juniper::graphql_object macro.
-pub fn build_object(args: TokenStream, body: TokenStream, error: GraphQLScope) -> TokenStream {
-    let definition = match create(args, body, error) {
-        Ok(definition) => definition,
-        Err(err) => return err.to_compile_error(),
-    };
-    definition.into_tokens()
-}
-
-/// Generate code for the juniper::graphql_subscription macro.
-pub fn build_subscription(
-    args: TokenStream,
-    body: TokenStream,
-    error: GraphQLScope,
-) -> TokenStream {
-    let definition = match create(args, body, error) {
-        Ok(definition) => definition,
-        Err(err) => return err.to_compile_error(),
-    };
-    definition.into_subscription_tokens()
-}
-
-fn create(
-    args: TokenStream,
-    body: TokenStream,
-    error: GraphQLScope,
-) -> syn::Result<util::GraphQLTypeDefiniton> {
-    let body_span = body.span();
-    let _impl = util::parse_impl::ImplBlock::parse(args, body)?;
-    let name = _impl
-        .attrs
-        .name
-        .clone()
-        .map(SpanContainer::into_inner)
-        .unwrap_or_else(|| _impl.type_ident.unraw().to_string());
-
-    let top_attrs = &_impl.attrs;
-
-    let scalar = _impl
-        .attrs
-        .scalar
-        .as_ref()
-        .map(|s| quote!( #s ))
-        .unwrap_or_else(|| quote!(::juniper::DefaultScalarValue));
-
-    let fields = _impl
-        .methods
-        .iter()
-        .filter_map(|method| {
-            let span = method.span();
-            let _type = match method.sig.output {
-                syn::ReturnType::Type(_, ref t) => *t.clone(),
-                syn::ReturnType::Default => {
-                    error.emit_custom(method.sig.span(), "return value required");
-                    return None;
-                }
-            };
-
-            let is_async = method.sig.asyncness.is_some();
-
-            let attrs = match util::FieldAttributes::from_attrs(
-                &method.attrs,
-                util::FieldAttributeParseMode::Impl,
-            ) {
-                Ok(attrs) => attrs,
-                Err(err) => {
-                    proc_macro_error::emit_error!(err);
-                    return None;
-                }
-            };
-
-            let parse_method =
-                _impl.parse_method(&method, true, |captured, arg_ident, is_mut: bool| {
-                    let arg_name = arg_ident.unraw().to_string();
-                    let ty = &captured.ty;
-
-                    let final_name = attrs
-                        .argument(&arg_name)
-                        .and_then(|attrs| attrs.rename.clone().map(|ident| ident.value()))
-                        .unwrap_or_else(|| {
-                            top_attrs
-                                .rename
-                                .unwrap_or(RenameRule::CamelCase)
-                                .apply(&arg_name)
-                        });
-
-                    let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
-
-                    if final_name.starts_with("__") {
-                        error.no_double_underscore(
-                            if let Some(name) = attrs
-                                .argument(&arg_name)
-                                .and_then(|attrs| attrs.rename.as_ref())
-                            {
-                                name.span_ident()
-                            } else {
-                                arg_ident.span()
-                            },
-                        );
-                    }
-
-                    let resolver = quote!(
-                        let #mut_modifier #arg_ident = args
-                            .get::<#ty>(#final_name)
-                            .unwrap_or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null);
-                    );
-
-                    let field_type = util::GraphQLTypeDefinitionFieldArg {
-                        description: attrs
-                            .argument(&arg_name)
-                            .and_then(|arg| arg.description.as_ref().map(|d| d.value())),
-                        default: attrs
-                            .argument(&arg_name)
-                            .and_then(|arg| arg.default.clone()),
-                        _type: ty.clone(),
-                        name: final_name,
-                    };
-                    Ok((resolver, field_type))
-                });
-
-            let (resolve_parts, args) = match parse_method {
-                Ok((resolve_parts, args)) => (resolve_parts, args),
-                Err(err) => {
-                    proc_macro_error::emit_error!(err);
-                    return None;
-                }
-            };
-
-            let body = &method.block;
-            let resolver_code = quote!(
-                #( #resolve_parts )*
-                #body
-            );
-
-            let ident = &method.sig.ident;
-            let name = attrs
-                .name
-                .clone()
-                .map(SpanContainer::into_inner)
-                .unwrap_or_else(|| {
-                    top_attrs
-                        .rename
-                        .unwrap_or(RenameRule::CamelCase)
-                        .apply(&ident.unraw().to_string())
-                });
-
-            if name.starts_with("__") {
-                error.no_double_underscore(if let Some(name) = attrs.name {
-                    name.span_ident()
-                } else {
-                    ident.span()
-                });
-            }
-
-            if let Some(default) = attrs.default {
-                error.unsupported_attribute_within(
-                    default.span_ident(),
-                    UnsupportedAttribute::Default,
-                );
-            }
-
-            Some(util::GraphQLTypeDefinitionField {
-                name,
-                _type,
-                args,
-                description: attrs.description.map(SpanContainer::into_inner),
-                deprecation: attrs.deprecation.map(SpanContainer::into_inner),
-                resolver_code,
-                is_type_inferred: false,
-                is_async,
-                default: None,
-                span,
-            })
-        })
-        .collect::<Vec<_>>();
-
-    // Early abort after checking all fields
-    proc_macro_error::abort_if_dirty();
-
-    if let Some(duplicates) =
-        crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name)
-    {
-        error.duplicate(duplicates.iter())
-    }
-
-    if !_impl.attrs.is_internal && name.starts_with("__") {
-        error.no_double_underscore(if let Some(name) = _impl.attrs.name {
-            name.span_ident()
-        } else {
-            _impl.type_ident.span()
-        });
-    }
-
-    if fields.is_empty() {
-        error.not_empty(body_span);
-    }
-
-    // Early abort after GraphQL properties
-    proc_macro_error::abort_if_dirty();
-
-    let definition = util::GraphQLTypeDefiniton {
-        name,
-        _type: *_impl.target_type.clone(),
-        scalar: _impl.attrs.scalar.map(SpanContainer::into_inner),
-        context: _impl.attrs.context.map(SpanContainer::into_inner),
-        description: _impl.description,
-        fields,
-        generics: _impl.generics.clone(),
-        interfaces: _impl
-            .attrs
-            .interfaces
-            .into_iter()
-            .map(SpanContainer::into_inner)
-            .collect(),
-        include_type_generics: false,
-        generic_scalar: true,
-        no_async: _impl.attrs.no_async.is_some(),
-    };
-
-    Ok(definition)
-}
diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs
index 38870ff28..e0b0729c9 100644
--- a/juniper_codegen/src/lib.rs
+++ b/juniper_codegen/src/lib.rs
@@ -108,13 +108,13 @@ macro_rules! try_merge_hashset {
 
 mod derive_enum;
 mod derive_input_object;
-mod derive_object;
 mod derive_scalar_value;
-mod impl_object;
 mod impl_scalar;
 
 mod common;
 mod graphql_interface;
+mod graphql_object;
+mod graphql_subscription;
 mod graphql_union;
 
 use proc_macro::TokenStream;
@@ -143,17 +143,6 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
     }
 }
 
-#[proc_macro_error]
-#[proc_macro_derive(GraphQLObject, attributes(graphql))]
-pub fn derive_object(input: TokenStream) -> TokenStream {
-    let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
-    let gen = derive_object::build_derive_object(ast, GraphQLScope::DeriveObject);
-    match gen {
-        Ok(gen) => gen.into(),
-        Err(err) => proc_macro_error::abort!(err),
-    }
-}
-
 /// This custom derive macro implements the #[derive(GraphQLScalarValue)]
 /// derive.
 ///
@@ -207,273 +196,6 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
     }
 }
 
-/**
-The `object` proc macro is the primary way of defining GraphQL resolvers
-that can not be implemented with the GraphQLObject derive.
-
-It enables you to write GraphQL field resolvers for a type by declaring a
-regular Rust `impl` block. Under the hood, the procedural macro implements
-the GraphQLType trait.
-
-`object` comes with many features that allow customization of
-your fields, all of which are detailed below.
-
-### Getting Started
-
-This simple example will show you the most basic use of `object`.
-More advanced use cases are introduced step by step.
-
-```
-// So we can declare it as a plain struct without any members.
-struct Query;
-
-// We prefix the impl Block with the procedural macro.
-#[juniper::graphql_object]
-impl Query {
-
-    // A **warning**: only GraphQL fields can be specified in this impl block.
-    // If you want to define normal methods on the struct,
-    // you have to do so in a separate, normal `impl` block.
-
-
-    // This defines a simple, static field which does not require any context.
-    // You can return any value that implements the `GraphQLType` trait.
-    // This trait is implemented for:
-    //  - basic scalar types like bool, &str, String, i32, f64
-    //  - GraphQL compatible wrappers like Option<_>, Vec<_>.
-    //  - types which use the `#derive[juniper::GraphQLObject]`
-    //  - `object` structs.
-    //
-    // An important note regarding naming:
-    // By default, field names will be converted to camel case.
-    // For your GraphQL queries, the field will be available as `apiVersion`.
-    //
-    // You can also manually customize the field name if required. (See below)
-    fn api_version() -> &'static str {
-        "0.1"
-    }
-
-    // This field takes two arguments.
-    // GraphQL arguments are just regular function parameters.
-    // **Note**: in Juniper, arguments are non-nullable by default.
-    //           for optional arguments, you have to specify them with Option<T>.
-    fn add(a: f64, b: f64, c: Option<f64>) -> f64 {
-        a + b + c.unwrap_or(0.0)
-    }
-}
-```
-
-## Accessing self
-
-```
-struct Person {
-    first_name: String,
-    last_name: String,
-}
-
-impl Person {
-    // The full name method is useful outside of GraphQL,
-    // so we define it as a normal method.
-    fn build_full_name(&self) -> String {
-        format!("{} {}", self.first_name, self.last_name)
-    }
-}
-
-#[juniper::graphql_object]
-impl Person {
-    fn first_name(&self) -> &str {
-        &self.first_name
-    }
-
-    fn last_name(&self) -> &str {
-        &self.last_name
-    }
-
-    fn full_name(&self) -> String {
-        self.build_full_name()
-    }
-}
-```
-
-## Context (+ Executor)
-
-You can specify a context that will be available across
-all your resolvers during query execution.
-
-The Context can be injected into your resolvers by just
-specifying an argument with the same type as the context
-(but as a reference).
-
-```
-
-# #[derive(juniper::GraphQLObject)] struct User { id: i32 }
-# struct DbPool;
-# impl DbPool { fn user(&self, id: i32) -> Option<User> { unimplemented!() } }
-
-struct Context {
-    db: DbPool,
-}
-
-// Mark our struct for juniper.
-impl juniper::Context for Context {}
-
-struct Query;
-
-#[juniper::graphql_object(
-    // Here we specify the context type for this object.
-    Context = Context,
-)]
-impl Query {
-    // Context is injected by specifying a argument
-    // as a reference to the Context.
-    fn user(context: &Context, id: i32) -> Option<User> {
-        context.db.user(id)
-    }
-
-    // You can also gain access to the executor, which
-    // allows you to do look aheads.
-    fn with_executor(executor: &Executor) -> bool {
-        let info = executor.look_ahead();
-        // ...
-        true
-    }
-}
-
-```
-
-## Customization (Documentation, Renaming, ...)
-
-```
-struct InternalQuery;
-
-// Doc comments can be used to specify graphql documentation.
-/// GRAPHQL DOCUMENTATION.
-/// More info for GraphQL users....
-#[juniper::graphql_object(
-    // You can rename the type for GraphQL by specifying the name here.
-    name = "Query",
-    // You can also specify a description here.
-    // If present, doc comments will be ignored.
-    description = "...",
-)]
-impl InternalQuery {
-    // Documentation doc comments also work on fields.
-    /// GraphQL description...
-    fn field_with_description() -> bool { true }
-
-    // Fields can also be customized with the #[graphql] attribute.
-    #[graphql(
-        // overwrite the public name
-        name = "actualFieldName",
-        // Can be used instead of doc comments.
-        description = "field description",
-    )]
-    fn internal_name() -> bool { true }
-
-    // Fields can be deprecated too.
-    #[graphql(
-        deprecated = "deprecatin info...",
-        // Note: just "deprecated," without a description works too.
-    )]
-    fn deprecated_field_simple() -> bool { true }
-
-
-    // Customizing field arguments is a little awkward right now.
-    // This will improve once [RFC 2564](https://github.com/rust-lang/rust/issues/60406)
-    // is implemented, which will allow attributes on function parameters.
-
-    #[graphql(
-        arguments(
-            arg1(
-                // You can specify default values.
-                // A default can be any valid expression that yields the right type.
-                default = true,
-                description = "Argument description....",
-            ),
-            arg2(
-                default = false,
-                description = "arg2 description...",
-            ),
-        ),
-    )]
-    fn args(arg1: bool, arg2: bool) -> bool {
-        arg1 && arg2
-    }
-}
-```
-
-## Lifetimes, Generics and custom Scalars
-
-Lifetimes work just like you'd expect.
-
-
-```
-struct WithLifetime<'a> {
-    value: &'a str,
-}
-
-#[juniper::graphql_object]
-impl<'a> WithLifetime<'a> {
-    fn value(&self) -> &str {
-        self.value
-    }
-}
-
-```
-
-Juniper has support for custom scalars.
-Mostly you will only need the default scalar type juniper::DefaultScalarValue.
-
-You can easily specify a custom scalar though.
-
-
-```
-
-# type MyCustomScalar = juniper::DefaultScalarValue;
-
-struct Query;
-
-#[juniper::graphql_object(
-    Scalar = MyCustomScalar,
-)]
-impl Query {
-    fn test(&self) -> i32 {
-        0
-    }
-}
-```
-
-## Raw identifiers
-
-You can use [raw identifiers](https://doc.rust-lang.org/stable/edition-guide/rust-2018/module-system/raw-identifiers.html)
-if you want a GrahpQL field that happens to be a Rust keyword:
-
-```
-struct User {
-    r#type: String,
-}
-
-#[juniper::graphql_object]
-impl User {
-    fn r#type(&self) -> &str {
-        &self.r#type
-    }
-}
-```
-
-*/
-#[proc_macro_error]
-#[proc_macro_attribute]
-pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream {
-    let args = proc_macro2::TokenStream::from(args);
-    let input = proc_macro2::TokenStream::from(input);
-    TokenStream::from(impl_object::build_object(
-        args,
-        input,
-        GraphQLScope::ImplObject,
-    ))
-}
-
 /// Expose GraphQL scalars
 ///
 /// The GraphQL language defines a number of built-in scalars: strings, numbers, and
@@ -495,7 +217,7 @@ pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream {
 /// struct UserID(String);
 ///
 /// #[juniper::graphql_scalar(
-///     // You can rename the type for GraphQL by specifying the name here.    
+///     // You can rename the type for GraphQL by specifying the name here.
 ///     name = "MyName",
 ///     // You can also specify a description here.
 ///     // If present, doc comments will be ignored.
@@ -535,36 +257,26 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
     }
 }
 
-/// A proc macro for defining a GraphQL subscription.
-#[proc_macro_error]
-#[proc_macro_attribute]
-pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStream {
-    let args = proc_macro2::TokenStream::from(args);
-    let input = proc_macro2::TokenStream::from(input);
-    TokenStream::from(impl_object::build_subscription(
-        args,
-        input,
-        GraphQLScope::ImplObject,
-    ))
-}
-
-/// `#[graphql_interface]` macro for generating a [GraphQL interface][1] implementation for traits
-/// and its implementers.
+/// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
+/// implementation for traits and its implementers.
 ///
-/// Specifying multiple `#[graphql_interface]` attributes on the same definition is totally okay.
-/// They all will be treated as a single attribute.
+/// Specifying multiple `#[graphql_interface]` attributes on the same definition
+/// is totally okay. They all will be treated as a single attribute.
 ///
-/// The main difference between [GraphQL interface][1] type and Rust trait is that the former serves
-/// both as an _abstraction_ and a _value downcastable to concrete implementers_, while in Rust, a
-/// trait is an _abstraction only_ and you need a separate type to downcast into a concrete
-/// implementer, like enum or [trait object][3], because trait doesn't represent a type itself.
-/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by default, however
-/// [trait object][3] may be used too (use `dyn` attribute argument for that).
+/// The main difference between [GraphQL interface][1] type and Rust trait is
+/// that the former serves both as an _abstraction_ and a _value downcastable to
+/// concrete implementers_, while in Rust, a trait is an _abstraction only_ and
+/// you need a separate type to downcast into a concrete implementer, like enum
+/// or [trait object][3], because trait doesn't represent a type itself.
+/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by
+/// default, however [trait object][3] may be used too (use `dyn` attribute
+/// argument for that).
 ///
-/// A __trait has to be [object safe][2]__ if its values are represented by [trait object][3],
-/// because schema resolvers will need to return that [trait object][3]. The [trait object][3] has
-/// to be [`Send`] and [`Sync`], and the macro automatically generate a convenien type alias for
-/// such [trait object][3].
+/// A __trait has to be [object safe][2]__ if its values are represented by
+/// [trait object][3], because schema resolvers will need to return that
+/// [trait object][3]. The [trait object][3] has to be [`Send`] and [`Sync`],
+/// and the macro automatically generate a convenien type alias for such
+/// [trait object][3].
 ///
 /// ```
 /// use juniper::{graphql_interface, GraphQLObject};
@@ -619,7 +331,7 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 ///
 /// # Custom name, description, deprecation and argument defaults
 ///
-/// The name of [GraphQL interface][1], its field, or a field argument may be overriden with a
+/// The name of [GraphQL interface][1], its field, or a field argument may be overridden with a
 /// `name` attribute's argument. By default, a type name is used or `camelCased` method/argument
 /// name.
 ///
@@ -627,13 +339,12 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 /// either with a `description`/`desc` attribute's argument, or with a regular Rust doc comment.
 ///
 /// A field of [GraphQL interface][1] may be deprecated by specifying a `deprecated` attribute's
-/// argument, or with regulat Rust `#[deprecated]` attribute.
+/// argument, or with regular Rust `#[deprecated]` attribute.
 ///
 /// The default value of a field argument may be specified with a `default` attribute argument (if
 /// no exact value is specified then [`Default::default`] is used).
 ///
 /// ```
-/// # #![allow(deprecated)]
 /// # use juniper::graphql_interface;
 /// #
 /// #[graphql_interface(name = "Character", desc = "Possible episode characters.")]
@@ -658,16 +369,72 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 /// }
 /// ```
 ///
+/// # Renaming policy
+///
+/// By default, all [GraphQL interface][1] fields and their arguments are renamed
+/// via `camelCase` policy (so `fn my_id(&self) -> String` becomes `myId` field
+/// in GraphQL schema, and so on). This complies with default GraphQL naming
+/// conventions [demonstrated in spec][0].
+///
+/// However, if you need for some reason apply another naming convention, it's
+/// possible to do by using `rename_all` attribute's argument. At the moment it
+/// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`,
+/// `none` (disables any renaming).
+///
+/// ```
+/// # use juniper::{graphql_interface, GraphQLObject};
+/// #
+/// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming
+/// trait Character {
+///     // NOTICE: In the generated GraphQL schema this field and its argument
+///     //         will be `detailed_info` and `info_kind`.
+///     fn detailed_info(&self, info_kind: String) -> String;
+/// }
+///
+/// #[derive(GraphQLObject)]
+/// #[graphql(impl = CharacterValue)]
+/// struct Human {
+///     id: String,
+///     home_planet: String,
+/// }
+/// #[graphql_interface]
+/// impl Character for Human {
+///     fn detailed_info(&self, info_kind: String) -> String {
+///         (info_kind == "planet")
+///             .then(|| &self.home_planet)
+///             .unwrap_or(&self.id)
+///             .clone()
+///     }
+/// }
+/// ```
+///
+/// # Ignoring trait methods
+///
+/// To omit some trait method to be assumed as a [GraphQL interface][1] field
+/// and ignore it, use an `ignore` attribute's argument directly on that method.
+///
+/// ```
+/// # use juniper::graphql_interface;
+/// #
+/// #[graphql_interface]
+/// trait Character {
+///     fn id(&self) -> &str;
+///
+///     #[graphql(ignore)]
+///     fn kaboom(&mut self);
+/// }
+/// ```
+///
 /// # Custom context
 ///
 /// By default, the generated implementation tries to infer [`Context`] type from signatures of
 /// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments.
 ///
 /// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly
-/// with `context`/`Context` attribute's argument.
+/// with `context` attribute's argument.
 ///
 /// If trait method represents a [GraphQL interface][1] field and its argument is named as `context`
-/// or `ctx` then this argument is assumed as [`Context`] and will be omited in GraphQL schema.
+/// or `ctx` then this argument is assumed as [`Context`] and will be omitted in GraphQL schema.
 /// Additionally, any argument may be marked as [`Context`] with a `context` attribute's argument.
 ///
 /// ```
@@ -723,7 +490,7 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 ///
 /// If an [`Executor`] is required in a trait method to resolve a [GraphQL interface][1] field,
 /// specify it as an argument named as `executor` or explicitly marked with an `executor`
-/// attribute's argument. Such method argument will be omited in GraphQL schema.
+/// attribute's argument. Such method argument will be omitted in GraphQL schema.
 ///
 /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so.
 ///
@@ -731,7 +498,7 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 /// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
 /// #
 /// // NOTICE: Specifying `ScalarValue` as existing type parameter.
-/// #[graphql_interface(for = Human, Scalar = S)]
+/// #[graphql_interface(for = Human, scalar = S)]
 /// trait Character<S: ScalarValue> {
 ///     async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
 ///     where
@@ -751,7 +518,7 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 ///     id: String,
 ///     name: String,
 /// }
-/// #[graphql_interface(Scalar = S)]
+/// #[graphql_interface(scalar = S)]
 /// impl<S: ScalarValue> Character<S> for Human {
 ///     async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
 ///     where
@@ -771,28 +538,29 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 ///
 /// # Custom `ScalarValue`
 ///
-/// By default, `#[graphql_interface]` macro generates code, which is generic over a [`ScalarValue`]
-/// type. This may introduce a problem when at least one of [GraphQL interface][1] implementers is
-/// restricted to a concrete [`ScalarValue`] type in its implementation. To resolve such problem, a
-/// concrete [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue`
+/// By default, `#[graphql_interface]` macro generates code, which is generic
+/// over a [`ScalarValue`] type. This may introduce a problem when at least one
+/// of [GraphQL interface][1] implementers is restricted to a concrete
+/// [`ScalarValue`] type in its implementation. To resolve such problem, a
+/// concrete [`ScalarValue`] type should be specified with a `scalar`
 /// attribute's argument.
 ///
 /// ```
 /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject};
 /// #
 /// // NOTICE: Removing `Scalar` argument will fail compilation.
-/// #[graphql_interface(for = [Human, Droid], Scalar = DefaultScalarValue)]
+/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)]
 /// trait Character {
 ///     fn id(&self) -> &str;
 /// }
 ///
 /// #[derive(GraphQLObject)]
-/// #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
+/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)]
 /// struct Human {
 ///     id: String,
 ///     home_planet: String,
 /// }
-/// #[graphql_interface(Scalar = DefaultScalarValue)]
+/// #[graphql_interface(scalar = DefaultScalarValue)]
 /// impl Character for Human {
 ///     fn id(&self) -> &str{
 ///         &self.id
@@ -800,12 +568,12 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 /// }
 ///
 /// #[derive(GraphQLObject)]
-/// #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
+/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)]
 /// struct Droid {
 ///     id: String,
 ///     primary_function: String,
 /// }
-/// #[graphql_interface(Scalar = DefaultScalarValue)]
+/// #[graphql_interface(scalar = DefaultScalarValue)]
 /// impl Character for Droid {
 ///     fn id(&self) -> &str {
 ///         &self.id
@@ -813,23 +581,6 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 /// }
 /// ```
 ///
-/// # Ignoring trait methods
-///
-/// To omit some trait method to be assumed as a [GraphQL interface][1] field and ignore it, use an
-/// `ignore`/`skip` attribute's argument directly on that method.
-///
-/// ```
-/// # use juniper::graphql_interface;
-/// #
-/// #[graphql_interface]
-/// trait Character {
-///     fn id(&self) -> &str;
-///
-///     #[graphql(ignore)]  // or `#[graphql(skip)]`, your choice
-///     fn kaboom(&mut self);
-/// }
-/// ```
-///
 /// # Downcasting
 ///
 /// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via
@@ -901,6 +652,7 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
 /// [`Context`]: juniper::Context
 /// [`Executor`]: juniper::Executor
 /// [`ScalarValue`]: juniper::ScalarValue
+/// [0]: https://spec.graphql.org/June2018
 /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
 /// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety
 /// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html
@@ -913,6 +665,521 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
         .into()
 }
 
+/// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1]
+/// implementation for structs.
+///
+/// The `#[graphql]` helper attribute is used for configuring the derived
+/// implementation. Specifying multiple `#[graphql]` attributes on the same
+/// definition is totally okay. They all will be treated as a single attribute.
+///
+/// ```
+/// use juniper::GraphQLObject;
+///
+/// #[derive(GraphQLObject)]
+/// struct Query {
+///     // NOTICE: By default, field names will be converted to `camelCase`.
+///     //         In the generated GraphQL schema this field will be available
+///     //         as `apiVersion`.
+///     api_version: &'static str,
+/// }
+/// ```
+///
+/// # Custom name, description and deprecation
+///
+/// The name of [GraphQL object][1] or its field may be overridden with a `name`
+/// attribute's argument. By default, a type name is used or `camelCased` field
+/// name.
+///
+/// The description of [GraphQL object][1] or its field may be specified either
+/// with a `description`/`desc` attribute's argument, or with a regular Rust doc
+/// comment.
+///
+/// A field of [GraphQL object][1] may be deprecated by specifying a
+/// `deprecated` attribute's argument, or with regular Rust `#[deprecated]`
+/// attribute.
+///
+/// ```
+/// # use juniper::GraphQLObject;
+/// #
+/// #[derive(GraphQLObject)]
+/// #[graphql(
+///     // Rename the type for GraphQL by specifying the name here.
+///     name = "Human",
+///     // You may also specify a description here.
+///     // If present, doc comments will be ignored.
+///     desc = "Possible episode human.",
+/// )]
+/// struct HumanWithAttrs {
+///     #[graphql(name = "id", desc = "ID of the human.")]
+///     #[graphql(deprecated = "Don't use it")]
+///     some_id: String,
+/// }
+///
+/// // Rust docs are used as GraphQL description.
+/// /// Possible episode human.
+/// #[derive(GraphQLObject)]
+/// struct HumanWithDocs {
+///     // Doc comments also work on fields.
+///     /// ID of the human.
+///     #[deprecated]
+///     id: String,
+/// }
+/// ```
+///
+/// # Renaming policy
+///
+/// By default, all [GraphQL object][1] fields are renamed via `camelCase`
+/// policy (so `api_version: String` becomes `apiVersion` field in GraphQL
+/// schema, and so on). This complies with default GraphQL naming conventions
+/// [demonstrated in spec][0].
+///
+/// However, if you need for some reason apply another naming convention, it's
+/// possible to do by using `rename_all` attribute's argument. At the moment it
+/// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`,
+/// `none` (disables any renaming).
+///
+/// ```
+/// # use juniper::GraphQLObject;
+/// #
+/// #[derive(GraphQLObject)]
+/// #[graphql(rename_all = "none")] // disables renaming
+/// struct Query {
+///     // NOTICE: In the generated GraphQL schema this field will be available
+///     //         as `api_version`.
+///     api_version: String,
+/// }
+/// ```
+///
+/// # Ignoring struct fields
+///
+/// To omit exposing a struct field in the GraphQL schema, use an `ignore`
+/// attribute's argument directly on that field.
+///
+/// ```
+/// # use juniper::GraphQLObject;
+/// #
+/// #[derive(GraphQLObject)]
+/// struct Human {
+///     id: String,
+///     #[graphql(ignore)]
+///     home_planet: String,
+/// }
+/// ```
+///
+/// # Custom `ScalarValue`
+///
+/// By default, `#[derive(GraphQLObject)]` macro generates code, which is
+/// generic over a [`ScalarValue`] type. This may introduce a problem when at
+/// least one of its fields is restricted to a concrete [`ScalarValue`] type in
+/// its implementation. To resolve such problem, a concrete [`ScalarValue`] type
+/// should be specified with a `scalar` attribute's argument.
+///
+/// ```
+/// # use juniper::{DefaultScalarValue, GraphQLObject};
+/// #
+/// #[derive(GraphQLObject)]
+/// // NOTICE: Removing `scalar` argument will fail compilation.
+/// #[graphql(scalar = DefaultScalarValue)]
+/// struct Human {
+///     id: String,
+///     helper: Droid,
+/// }
+///
+/// #[derive(GraphQLObject)]
+/// #[graphql(scalar = DefaultScalarValue)]
+/// struct Droid {
+///     id: String,
+/// }
+/// ```
+///
+/// [`ScalarValue`]: juniper::ScalarValue
+/// [1]: https://spec.graphql.org/June2018/#sec-Objects
+#[proc_macro_error]
+#[proc_macro_derive(GraphQLObject, attributes(graphql))]
+pub fn derive_object(body: TokenStream) -> TokenStream {
+    self::graphql_object::derive::expand(body.into())
+        .unwrap_or_abort()
+        .into()
+}
+
+/// `#[graphql_object]` macro for generating a [GraphQL object][1]
+/// implementation for structs with computable field resolvers (declared via
+/// a regular Rust `impl` block).
+///
+/// It enables you to write GraphQL field resolvers for a type by declaring a
+/// regular Rust `impl` block. Under the hood, the macro implements
+/// [`GraphQLType`]/[`GraphQLValue`] traits.
+///
+/// Specifying multiple `#[graphql_object]` attributes on the same definition
+/// is totally okay. They all will be treated as a single attribute.
+///
+/// ```
+/// use juniper::graphql_object;
+///
+/// // We can declare the type as a plain struct without any members.
+/// struct Query;
+///
+/// #[graphql_object]
+/// impl Query {
+///     // WARNING: Only GraphQL fields can be specified in this `impl` block.
+///     //          If normal methods are required on the struct, they can be
+///     //          defined either in a separate "normal" `impl` block, or
+///     //          marked with `#[graphql(ignore)]` attribute.
+///
+///     // This defines a simple, static field which does not require any
+///     // context.
+///     // Such field can return any value that implements `GraphQLType` and
+///     // `GraphQLValue` traits.
+///     //
+///     // NOTICE: By default, field names will be converted to `camelCase`.
+///     //         In the generated GraphQL schema this field will be available
+///     //         as `apiVersion`.
+///     fn api_version() -> &'static str {
+///         "0.1"
+///     }
+///
+///     // This field takes two arguments.
+///     // GraphQL arguments are just regular function parameters.
+///     //
+///     // NOTICE: In `juniper`, arguments are non-nullable by default. For
+///     //         optional arguments, you have to specify them as `Option<_>`.
+///     async fn add(a: f64, b: f64, c: Option<f64>) -> f64 {
+///         a + b + c.unwrap_or(0.0)
+///     }
+/// }
+/// ```
+///
+/// # Accessing self
+///
+/// Fields may also have a `self` receiver.
+///
+/// ```
+/// # use juniper::graphql_object;
+/// #
+/// struct Person {
+///     first_name: String,
+///     last_name: String,
+/// }
+///
+/// #[graphql_object]
+/// impl Person {
+///     fn first_name(&self) -> &str {
+///         &self.first_name
+///     }
+///
+///     fn last_name(&self) -> &str {
+///         &self.last_name
+///     }
+///
+///     fn full_name(&self) -> String {
+///         self.build_full_name()
+///     }
+///
+///     // This method is useful only to define GraphQL fields, but is not
+///     // a field itself, so we ignore it in schema.
+///     #[graphql(ignore)]
+///     fn build_full_name(&self) -> String {
+///         format!("{} {}", self.first_name, self.last_name)
+///     }
+/// }
+/// ```
+///
+/// # Custom name, description, deprecation and argument defaults
+///
+/// The name of [GraphQL object][1], its field, or a field argument may be
+/// overridden with a `name` attribute's argument. By default, a type name is
+/// used or `camelCased` method/argument name.
+///
+/// The description of [GraphQL object][1], its field, or a field argument may
+/// be specified either with a `description`/`desc` attribute's argument, or
+/// with a regular Rust doc comment.
+///
+/// A field of [GraphQL object][1] may be deprecated by specifying a
+/// `deprecated` attribute's argument, or with regular Rust `#[deprecated]`
+/// attribute.
+///
+/// The default value of a field argument may be specified with a `default`
+/// attribute argument (if no exact value is specified then [`Default::default`]
+/// is used).
+///
+/// ```
+/// # use juniper::graphql_object;
+/// #
+/// struct HumanWithAttrs;
+///
+/// #[graphql_object(
+///     // Rename the type for GraphQL by specifying the name here.
+///     name = "Human",
+///     // You may also specify a description here.
+///     // If present, doc comments will be ignored.
+///     desc = "Possible episode human.",
+/// )]
+/// impl HumanWithAttrs {
+///     #[graphql(name = "id", desc = "ID of the human.")]
+///     #[graphql(deprecated = "Don't use it")]
+///     fn some_id(
+///         &self,
+///         #[graphql(name = "number", desc = "Arbitrary number.")]
+///         // You may specify default values.
+///         // A default can be any valid expression that yields the right type.
+///         #[graphql(default = 5)]
+///         num: i32,
+///     ) -> &str {
+///         "Don't use me!"
+///     }
+/// }
+///
+/// struct HumanWithDocs;
+///
+/// // Rust docs are used as GraphQL description.
+/// /// Possible episode human.
+/// #[graphql_object]
+/// impl HumanWithDocs {
+///     // Doc comments also work on fields.
+///     /// ID of the human.
+///     #[deprecated]
+///     fn id(
+///         &self,
+///         // If expression is not specified then `Default::default()` is used.
+///         #[graphql(default)] num: i32,
+///     ) -> &str {
+///         "Deprecated"
+///     }
+/// }
+/// ```
+///
+/// # Renaming policy
+///
+/// By default, all [GraphQL object][1] fields and their arguments are renamed
+/// via `camelCase` policy (so `fn api_version() -> String` becomes `apiVersion`
+/// field in GraphQL schema, and so on). This complies with default GraphQL
+/// naming conventions [demonstrated in spec][0].
+///
+/// However, if you need for some reason apply another naming convention, it's
+/// possible to do by using `rename_all` attribute's argument. At the moment it
+/// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`,
+/// `none` (disables any renaming).
+///
+/// ```
+/// # use juniper::graphql_object;
+/// #
+/// struct Query;
+///
+/// #[graphql_object(rename_all = "none")] // disables renaming
+/// impl Query {
+///     // NOTICE: In the generated GraphQL schema this field will be available
+///     //         as `api_version`.
+///     fn api_version() -> &'static str {
+///         "0.1"
+///     }
+///
+///     // NOTICE: In the generated GraphQL schema these field arguments will be
+///     //         available as `arg_a` and `arg_b`.
+///     async fn add(arg_a: f64, arg_b: f64, c: Option<f64>) -> f64 {
+///         arg_a + arg_b + c.unwrap_or(0.0)
+///     }
+/// }
+/// ```
+///
+/// # Ignoring methods
+///
+/// To omit some method to be assumed as a [GraphQL object][1] field and ignore
+/// it, use an `ignore` attribute's argument directly on that method.
+///
+/// ```
+/// # use juniper::graphql_object;
+/// #
+/// struct Human(String);
+///
+/// #[graphql_object]
+/// impl Human {
+///     fn id(&self) -> &str {
+///         &self.0
+///     }
+///
+///     #[graphql(ignore)]
+///     fn kaboom(&mut self) {}
+/// }
+/// ```
+///
+/// # Custom context
+///
+/// By default, the generated implementation tries to infer [`Context`] type
+/// from signatures of `impl` block methods, and uses [unit type `()`][4] if
+/// signatures contains no [`Context`] arguments.
+///
+/// If [`Context`] type cannot be inferred or is inferred incorrectly, then
+/// specify it explicitly with `context` attribute's argument.
+///
+/// If method argument is named as `context` or `ctx` then this argument is
+/// assumed as [`Context`] and will be omitted in GraphQL schema.
+/// Additionally, any argument may be marked as [`Context`] with a `context`
+/// attribute's argument.
+///
+/// ```
+/// # use std::collections::HashMap;
+/// # use juniper::graphql_object;
+/// #
+/// struct Database {
+///     humans: HashMap<String, Human>,
+/// }
+/// impl juniper::Context for Database {}
+///
+/// struct Human {
+///     id: String,
+///     home_planet: String,
+/// }
+///
+/// #[graphql_object(context = Database)]
+/// impl Human {
+///     fn id<'db>(&self, context: &'db Database) -> Option<&'db str> {
+///         context.humans.get(&self.id).map(|h| h.id.as_str())
+///     }
+///     fn info<'db>(&self, context: &'db Database) -> Option<&'db str> {
+///         context.humans.get(&self.id).map(|h| h.home_planet.as_str())
+///     }
+/// }
+/// ```
+///
+/// # Using `Executor`
+///
+/// If an [`Executor`] is required in a method to resolve a [GraphQL object][1]
+/// field, specify it as an argument named as `executor` or explicitly marked
+/// with an `executor` attribute's argument. Such method argument will be
+/// omitted in GraphQL schema.
+///
+/// However, this requires to explicitly parametrize over [`ScalarValue`], as
+/// [`Executor`] does so.
+///
+/// ```
+/// # use juniper::{graphql_object, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
+/// #
+/// struct Human {
+///     name: String,
+/// }
+///
+/// // NOTICE: Specifying `ScalarValue` as custom named type parameter.
+/// //         Its name should be similar to the one used in methods.
+/// #[graphql_object(scalar = S: ScalarValue)]
+/// impl Human {
+///     async fn id<'a, S: ScalarValue>(
+///         &self,
+///         executor: &'a Executor<'_, '_, (), S>,
+///     ) -> &'a str {
+///         executor.look_ahead().field_name()
+///     }
+///
+///     fn name<'b, S: ScalarValue>(
+///         &'b self,
+///         #[graphql(executor)] _another: &Executor<'_, '_, (), S>,
+///     ) -> &'b str {
+///         &self.name
+///     }
+/// }
+/// ```
+///
+/// # Custom `ScalarValue`
+///
+/// By default, `#[graphql_object]` macro generates code, which is generic over
+/// a [`ScalarValue`] type. This may introduce a problem when at least one of
+/// its fields is restricted to a concrete [`ScalarValue`] type in its
+/// implementation. To resolve such problem, a concrete [`ScalarValue`] type
+/// should be specified with a `scalar` attribute's argument.
+///
+/// ```
+/// # use juniper::{graphql_object, DefaultScalarValue, GraphQLObject};
+/// #
+/// struct Human(String);
+///
+/// // NOTICE: Removing `scalar` argument will fail compilation.
+/// #[graphql_object(scalar = DefaultScalarValue)]
+/// impl Human {
+///     fn id(&self) -> &str {
+///         &self.0
+///     }
+///
+///     fn helper(&self) -> Droid {
+///         Droid {
+///             id: self.0.clone(),
+///         }
+///     }
+/// }
+///
+/// #[derive(GraphQLObject)]
+/// #[graphql(scalar = DefaultScalarValue)]
+/// struct Droid {
+///     id: String,
+/// }
+/// ```
+///
+/// [`Context`]: juniper::Context
+/// [`Executor`]: juniper::Executor
+/// [`GraphQLType`]: juniper::GraphQLType
+/// [`GraphQLValue`]: juniper::GraphQLValue
+/// [`ScalarValue`]: juniper::ScalarValue
+/// [0]: https://spec.graphql.org/June2018
+/// [1]: https://spec.graphql.org/June2018/#sec-Objects
+#[proc_macro_error]
+#[proc_macro_attribute]
+pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream {
+    self::graphql_object::attr::expand(attr.into(), body.into())
+        .unwrap_or_abort()
+        .into()
+}
+
+/// `#[graphql_subscription]` macro for generating a [GraphQL subscription][1]
+/// implementation for structs with computable field resolvers (declared via
+/// a regular Rust `impl` block).
+///
+/// It enables you to write GraphQL field resolvers for a type by declaring a
+/// regular Rust `impl` block. Under the hood, the macro implements
+/// [`GraphQLType`]/[`GraphQLSubscriptionValue`] traits.
+///
+/// Specifying multiple `#[graphql_subscription]` attributes on the same
+/// definition is totally okay. They all will be treated as a single attribute.
+///
+/// This macro is similar to [`#[graphql_object]` macro](macro@graphql_object)
+/// and has all its properties, but requires methods to be `async` and return
+/// [`Stream`] of values instead of a value itself.
+///
+/// ```
+/// # use futures::stream::{self, BoxStream};
+/// use juniper::graphql_subscription;
+///
+/// // We can declare the type as a plain struct without any members.
+/// struct Subscription;
+///
+/// #[graphql_subscription]
+/// impl Subscription {
+///     // WARNING: Only GraphQL fields can be specified in this `impl` block.
+///     //          If normal methods are required on the struct, they can be
+///     //          defined either in a separate "normal" `impl` block, or
+///     //          marked with `#[graphql(ignore)]` attribute.
+///
+///     // This defines a simple, static field which does not require any
+///     // context.
+///     // Such field can return a `Stream` of any value implementing
+///     // `GraphQLType` and `GraphQLValue` traits.
+///     //
+///     // NOTICE: Method must be `async`.
+///     async fn api_version() -> BoxStream<'static, &'static str> {
+///         Box::pin(stream::once(async { "0.1" }))
+///     }
+/// }
+/// ```
+///
+/// [`GraphQLType`]: juniper::GraphQLType
+/// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue
+/// [`Stream`]: futures::Stream
+/// [1]: https://spec.graphql.org/June2018/#sec-Subscription
+#[proc_macro_error]
+#[proc_macro_attribute]
+pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream {
+    self::graphql_subscription::attr::expand(attr.into(), body.into())
+        .unwrap_or_abort()
+        .into()
+}
+
 /// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and
 /// structs.
 ///
@@ -995,7 +1262,7 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
 ///
 /// By default, the generated implementation uses [unit type `()`][4] as [`Context`]. To use a
 /// custom [`Context`] type for [GraphQL union][1] variants types or external resolver functions,
-/// specify it with `context`/`Context` attribute's argument.
+/// specify it with `context` attribute's argument.
 ///
 /// ```
 /// # use juniper::{GraphQLObject, GraphQLUnion};
@@ -1027,17 +1294,17 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
 ///
 /// # Custom `ScalarValue`
 ///
-/// By default, this macro generates code, which is generic over a [`ScalarValue`] type.
-/// This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a
-/// concrete [`ScalarValue`] type in its implementation. To resolve such problem, a concrete
-/// [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's
-/// argument.
+/// By default, this macro generates code, which is generic over a
+/// [`ScalarValue`] type. This may introduce a problem when at least one of
+/// [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`] type
+/// in its implementation. To resolve such problem, a concrete [`ScalarValue`]
+/// type should be specified with a `scalar` attribute's argument.
 ///
 /// ```
 /// # use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
 /// #
 /// #[derive(GraphQLObject)]
-/// #[graphql(Scalar = DefaultScalarValue)]
+/// #[graphql(scalar = DefaultScalarValue)]
 /// struct Human {
 ///     id: String,
 ///     home_planet: String,
@@ -1051,7 +1318,7 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
 ///
 /// // NOTICE: Removing `Scalar` argument will fail compilation.
 /// #[derive(GraphQLUnion)]
-/// #[graphql(Scalar = DefaultScalarValue)]
+/// #[graphql(scalar = DefaultScalarValue)]
 /// enum Character {
 ///     Human(Human),
 ///     Droid(Droid),
@@ -1060,8 +1327,8 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
 ///
 /// # Ignoring enum variants
 ///
-/// To omit exposing an enum variant in the GraphQL schema, use an `ignore`/`skip` attribute's
-/// argument directly on that variant.
+/// To omit exposing an enum variant in the GraphQL schema, use an `ignore`
+/// attribute's argument directly on that variant.
 ///
 /// > __WARNING__:
 /// > It's the _library user's responsibility_ to ensure that ignored enum variant is _never_
@@ -1089,7 +1356,7 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
 ///     Human(Human),
 ///     Droid(Droid),
 ///     #[from(ignore)]
-///     #[graphql(ignore)]  // or `#[graphql(skip)]`, your choice
+///     #[graphql(ignore)]
 ///     _State(PhantomData<S>),
 /// }
 /// ```
@@ -1215,8 +1482,8 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
 /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html
 #[proc_macro_error]
 #[proc_macro_derive(GraphQLUnion, attributes(graphql))]
-pub fn derive_union(input: TokenStream) -> TokenStream {
-    self::graphql_union::derive::expand(input.into())
+pub fn derive_union(body: TokenStream) -> TokenStream {
+    self::graphql_union::derive::expand(body.into())
         .unwrap_or_abort()
         .into()
 }
@@ -1319,7 +1586,7 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
 /// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments.
 ///
 /// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly
-/// with `context`/`Context` attribute's argument.
+/// with `context` attribute's argument.
 ///
 /// ```
 /// # use std::collections::HashMap;
@@ -1366,17 +1633,17 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
 ///
 /// # Custom `ScalarValue`
 ///
-/// By default, `#[graphql_union]` macro generates code, which is generic over a [`ScalarValue`]
-/// type. This may introduce a problem when at least one of [GraphQL union][1] variants is
-/// restricted to a concrete [`ScalarValue`] type in its implementation. To resolve such problem, a
-/// concrete [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue`
-/// attribute's argument.
+/// By default, `#[graphql_union]` macro generates code, which is generic over
+/// a [`ScalarValue`] type. This may introduce a problem when at least one of
+/// [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`] type
+/// in its implementation. To resolve such problem, a concrete [`ScalarValue`]
+/// type should be specified with a `scalar` attribute's argument.
 ///
 /// ```
 /// # use juniper::{graphql_union, DefaultScalarValue, GraphQLObject};
 /// #
 /// #[derive(GraphQLObject)]
-/// #[graphql(Scalar = DefaultScalarValue)]
+/// #[graphql(scalar = DefaultScalarValue)]
 /// struct Human {
 ///     id: String,
 ///     home_planet: String,
@@ -1389,7 +1656,7 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
 /// }
 ///
 /// // NOTICE: Removing `Scalar` argument will fail compilation.
-/// #[graphql_union(Scalar = DefaultScalarValue)]
+/// #[graphql_union(scalar = DefaultScalarValue)]
 /// trait Character {
 ///     fn as_human(&self) -> Option<&Human> { None }
 ///     fn as_droid(&self) -> Option<&Droid> { None }
@@ -1401,8 +1668,8 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
 ///
 /// # Ignoring trait methods
 ///
-/// To omit some trait method to be assumed as a [GraphQL union][1] variant and ignore it, use an
-/// `ignore`/`skip` attribute's argument directly on that method.
+/// To omit some trait method to be assumed as a [GraphQL union][1] variant and
+/// ignore it, use an `ignore` attribute's argument directly on that method.
 ///
 /// ```
 /// # use juniper::{graphql_union, GraphQLObject};
@@ -1423,7 +1690,7 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
 /// trait Character {
 ///     fn as_human(&self) -> Option<&Human> { None }
 ///     fn as_droid(&self) -> Option<&Droid> { None }
-///     #[graphql(ignore)]  // or `#[graphql(skip)]`, your choice
+///     #[graphql(ignore)]
 ///     fn id(&self) -> &str;
 /// }
 /// #
diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs
index 68c878ffa..f15e180ef 100644
--- a/juniper_codegen/src/result.rs
+++ b/juniper_codegen/src/result.rs
@@ -11,22 +11,22 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/";
 #[allow(unused_variables)]
 pub enum GraphQLScope {
     InterfaceAttr,
+    ObjectAttr,
+    ObjectDerive,
     UnionAttr,
     UnionDerive,
-    DeriveObject,
     DeriveInputObject,
     DeriveEnum,
     DeriveScalar,
     ImplScalar,
-    ImplObject,
 }
 
 impl GraphQLScope {
     pub fn spec_section(&self) -> &str {
         match self {
             Self::InterfaceAttr => "#sec-Interfaces",
+            Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
             Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
-            Self::DeriveObject | Self::ImplObject => "#sec-Objects",
             Self::DeriveInputObject => "#sec-Input-Objects",
             Self::DeriveEnum => "#sec-Enums",
             Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
@@ -38,8 +38,8 @@ impl fmt::Display for GraphQLScope {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let name = match self {
             Self::InterfaceAttr => "interface",
+            Self::ObjectAttr | Self::ObjectDerive => "object",
             Self::UnionAttr | Self::UnionDerive => "union",
-            Self::DeriveObject | Self::ImplObject => "object",
             Self::DeriveInputObject => "input object",
             Self::DeriveEnum => "enum",
             Self::DeriveScalar | Self::ImplScalar => "scalar",
@@ -69,6 +69,11 @@ impl GraphQLScope {
             .note(self.spec_link())
     }
 
+    pub fn error(&self, err: syn::Error) -> Diagnostic {
+        Diagnostic::spanned(err.span(), Level::Error, format!("{} {}", self, err))
+            .note(self.spec_link())
+    }
+
     pub fn emit_custom<S: AsRef<str>>(&self, span: Span, msg: S) {
         self.custom(span, msg).emit()
     }
diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs
index 63ac9c5b2..5648b8c30 100644
--- a/juniper_codegen/src/util/mod.rs
+++ b/juniper_codegen/src/util/mod.rs
@@ -1,10 +1,9 @@
 #![allow(clippy::single_match)]
 
 pub mod duplicate;
-pub mod parse_impl;
 pub mod span_container;
 
-use std::{collections::HashMap, str::FromStr};
+use std::{collections::HashMap, convert::TryFrom, str::FromStr};
 
 use proc_macro2::{Span, TokenStream};
 use proc_macro_error::abort;
@@ -21,61 +20,12 @@ use syn::{
 
 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.
-pub fn name_of_type(ty: &syn::Type) -> Option<syn::Ident> {
-    let path_opt = match ty {
-        syn::Type::Path(ref type_path) => Some(&type_path.path),
-        syn::Type::Reference(ref reference) => match &*reference.elem {
-            syn::Type::Path(ref type_path) => Some(&type_path.path),
-            syn::Type::TraitObject(ref trait_obj) => {
-                match trait_obj.bounds.iter().next().unwrap() {
-                    syn::TypeParamBound::Trait(ref trait_bound) => Some(&trait_bound.path),
-                    _ => None,
-                }
-            }
-            _ => None,
-        },
-        _ => None,
-    };
-    let path = path_opt?;
-
-    path.segments
-        .iter()
-        .last()
-        .map(|segment| segment.ident.clone())
-}
-
 /// Compares a path to a one-segment string value,
 /// return true if equal.
 pub fn path_eq_single(path: &syn::Path, value: &str) -> bool {
     path.segments.len() == 1 && path.segments[0].ident == value
 }
 
-/// Check if a type is a reference to another type.
-pub fn type_is_ref_of(ty: &syn::Type, target: &syn::Type) -> bool {
-    match ty {
-        syn::Type::Reference(_ref) => &*_ref.elem == target,
-        _ => false,
-    }
-}
-
-/// Check if a Type is a simple identifier.
-pub fn type_is_identifier(ty: &syn::Type, name: &str) -> bool {
-    match ty {
-        syn::Type::Path(ref type_path) => path_eq_single(&type_path.path, name),
-        _ => false,
-    }
-}
-
-/// Check if a Type is a reference to a given identifier.
-pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool {
-    match ty {
-        syn::Type::Reference(_ref) => type_is_identifier(&*_ref.elem, name),
-        _ => false,
-    }
-}
-
 #[derive(Debug)]
 pub struct DeprecationAttr {
     pub reason: Option<String>,
@@ -295,7 +245,7 @@ pub fn is_valid_name(field_name: &str) -> bool {
 }
 
 /// The different possible ways to change case of fields in a struct, or variants in an enum.
-#[derive(Copy, Clone, PartialEq, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub enum RenameRule {
     /// Don't apply a default rename rule.
     None,
@@ -328,6 +278,20 @@ impl FromStr for RenameRule {
     }
 }
 
+impl TryFrom<syn::LitStr> for RenameRule {
+    type Error = syn::Error;
+
+    fn try_from(lit: syn::LitStr) -> syn::Result<Self> {
+        Self::from_str(&lit.value()).map_err(|_| syn::Error::new(lit.span(), "unknown rename rule"))
+    }
+}
+
+impl Parse for RenameRule {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        Self::try_from(input.parse::<syn::LitStr>()?)
+    }
+}
+
 #[derive(Default, Debug)]
 pub struct ObjectAttributes {
     pub name: Option<SpanContainer<String>>,
@@ -341,7 +305,7 @@ pub struct ObjectAttributes {
 }
 
 impl Parse for ObjectAttributes {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
         let mut output = Self::default();
 
         while !input.is_empty() {
@@ -400,13 +364,8 @@ impl Parse for ObjectAttributes {
                     output.is_internal = true;
                 }
                 "rename" => {
-                    input.parse::<syn::Token![=]>()?;
-                    let val = input.parse::<syn::LitStr>()?;
-                    if let Ok(rename) = RenameRule::from_str(&val.value()) {
-                        output.rename = Some(rename);
-                    } else {
-                        return Err(syn::Error::new(val.span(), "unknown rename rule"));
-                    }
+                    input.parse::<token::Eq>()?;
+                    output.rename = Some(input.parse::<RenameRule>()?);
                 }
                 _ => {
                     return Err(syn::Error::new(ident.span(), "unknown attribute"));
@@ -449,7 +408,7 @@ pub struct FieldAttributeArgument {
 }
 
 impl Parse for FieldAttributeArgument {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
         let name = input.parse::<Ident>()?.unraw();
 
         let mut arg = Self {
@@ -490,7 +449,6 @@ impl Parse for FieldAttributeArgument {
 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
 pub enum FieldAttributeParseMode {
     Object,
-    Impl,
 }
 
 enum FieldAttribute {
@@ -503,7 +461,7 @@ enum FieldAttribute {
 }
 
 impl Parse for FieldAttribute {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
         let ident = input.parse::<syn::Ident>()?;
 
         match ident.to_string().as_str() {
@@ -593,7 +551,7 @@ pub struct FieldAttributes {
 }
 
 impl Parse for FieldAttributes {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
         let items = Punctuated::<FieldAttribute, token::Comma>::parse_terminated(&input)?;
 
         let mut output = Self::default();
@@ -654,10 +612,6 @@ impl FieldAttributes {
 
         Ok(output)
     }
-
-    pub fn argument(&self, name: &str) -> Option<&FieldAttributeArgument> {
-        self.arguments.get(name)
-    }
 }
 
 #[derive(Debug)]
@@ -729,710 +683,6 @@ impl GraphQLTypeDefiniton {
         self.fields.iter().any(|field| field.is_async)
     }
 
-    pub fn into_tokens(self) -> TokenStream {
-        let name = &self.name;
-        let ty = &self._type;
-        let context = self
-            .context
-            .as_ref()
-            .map(|ctx| quote!( #ctx ))
-            .unwrap_or_else(|| quote!(()));
-
-        let field_definitions = self.fields.iter().map(|field| {
-            let args = field.args.iter().map(|arg| {
-                let arg_type = &arg._type;
-                let arg_name = &arg.name;
-
-                let description = match arg.description.as_ref() {
-                    Some(value) => quote!( .description( #value ) ),
-                    None => quote!(),
-                };
-
-                // Code.
-                match arg.default.as_ref() {
-                    Some(value) => quote!(
-                        .argument(
-                            registry.arg_with_default::<#arg_type>(#arg_name, &#value, info)
-                                #description
-                        )
-                    ),
-                    None => quote!(
-                        .argument(
-                            registry.arg::<#arg_type>(#arg_name, info)
-                                #description
-                        )
-                    ),
-                }
-            });
-
-            let description = match field.description.as_ref() {
-                Some(description) => quote!( .description(#description) ),
-                None => quote!(),
-            };
-
-            let deprecation = match field.deprecation.as_ref() {
-                Some(deprecation) => {
-                    if let Some(reason) = deprecation.reason.as_ref() {
-                        quote!( .deprecated(Some(#reason)) )
-                    } else {
-                        quote!( .deprecated(None) )
-                    }
-                }
-                None => quote!(),
-            };
-
-            let field_name = &field.name;
-
-            let _type = &field._type;
-            quote! {
-                registry
-                    .field_convert::<#_type, _, Self::Context>(#field_name, info)
-                    #(#args)*
-                    #description
-                    #deprecation
-            }
-        });
-
-        let scalar = self
-            .scalar
-            .as_ref()
-            .map(|s| quote!( #s ))
-            .unwrap_or_else(|| {
-                if self.generic_scalar {
-                    // If generic_scalar is true, we always insert a generic scalar.
-                    // See more comments below.
-                    quote!(__S)
-                } else {
-                    quote!(::juniper::DefaultScalarValue)
-                }
-            });
-
-        let resolve_matches = self.fields.iter().map(|field| {
-            let name = &field.name;
-            let code = &field.resolver_code;
-
-            if field.is_async {
-                quote!(
-                    #name => {
-                        panic!("Tried to resolve async field {} on type {:?} with a sync resolver",
-                            #name,
-                            <Self as ::juniper::GraphQLType<#scalar>>::name(_info)
-                        );
-                    },
-                )
-            } else {
-                let _type = if field.is_type_inferred {
-                    quote!()
-                } else {
-                    let _type = &field._type;
-                    quote!(: #_type)
-                };
-                quote!(
-                    #name => {
-                        let res #_type = (|| { #code })();
-                        ::juniper::IntoResolvable::into(
-                            res,
-                            executor.context()
-                        )
-                            .and_then(|res| {
-                                match res {
-                                    Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
-                                    None => Ok(::juniper::Value::null()),
-                                }
-                            })
-                    },
-                )
-            }
-        });
-
-        let description = self
-            .description
-            .as_ref()
-            .map(|description| quote!( .description(#description) ));
-
-        let interfaces = if !self.interfaces.is_empty() {
-            let interfaces_ty = &self.interfaces;
-
-            Some(quote!(
-                .interfaces(&[
-                    #( registry.get_type::<#interfaces_ty>(&()) ,)*
-                ])
-            ))
-        } else {
-            None
-        };
-
-        // Preserve the original type_generics before modification,
-        // since alteration makes them invalid if self.generic_scalar
-        // is specified.
-        let (_, type_generics, _) = self.generics.split_for_impl();
-
-        let mut generics = self.generics.clone();
-
-        if self.scalar.is_none() && self.generic_scalar {
-            // No custom scalar specified, but always generic specified.
-            // Therefore we inject the generic scalar.
-            generics.params.push(parse_quote!(__S));
-            generics
-                .make_where_clause()
-                .predicates
-                .push(parse_quote!(__S: ::juniper::ScalarValue));
-        }
-
-        let type_generics_tokens = if self.include_type_generics {
-            Some(type_generics)
-        } else {
-            None
-        };
-        let (impl_generics, _, where_clause) = generics.split_for_impl();
-
-        let resolve_field_async = {
-            let resolve_matches_async = self.fields.iter().map(|field| {
-                let name = &field.name;
-                let code = &field.resolver_code;
-                let _type = if field.is_type_inferred {
-                    quote!()
-                } else {
-                    let _type = &field._type;
-                    quote!(: #_type)
-                };
-
-                if field.is_async {
-                    quote!(
-                        #name => {
-                            let f = async move {
-                                let res #_type = async move { #code }.await;
-
-                                let inner_res = ::juniper::IntoResolvable::into(
-                                    res,
-                                    executor.context()
-                                );
-                                match inner_res {
-                                    Ok(Some((ctx, r))) => {
-                                        let subexec = executor
-                                            .replaced_context(ctx);
-                                        subexec.resolve_with_ctx_async(&(), &r)
-                                            .await
-                                    },
-                                    Ok(None) => Ok(::juniper::Value::null()),
-                                    Err(e) => Err(e),
-                                }
-                            };
-                            Box::pin(f)
-                        },
-                    )
-                } else {
-                    let inner = if !self.no_async {
-                        quote!(
-                            let f = async move {
-                                match res2 {
-                                    Ok(Some((ctx, r))) => {
-                                        let sub = executor.replaced_context(ctx);
-                                        sub.resolve_with_ctx_async(&(), &r).await
-                                    },
-                                    Ok(None) => Ok(::juniper::Value::null()),
-                                    Err(e) => Err(e),
-                                }
-                            };
-                            use ::juniper::futures::future;
-                            future::FutureExt::boxed(f)
-                        )
-                    } else {
-                        quote!(
-                            let v = match res2 {
-                                Ok(Some((ctx, r))) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
-                                Ok(None) => Ok(::juniper::Value::null()),
-                                Err(e) => Err(e),
-                            };
-                            use ::juniper::futures::future;
-                            Box::pin(future::ready(v))
-                        )
-                    };
-
-                    quote!(
-                        #name => {
-                            let res #_type = (||{ #code })();
-                            let res2 = ::juniper::IntoResolvable::into(
-                                res,
-                                executor.context()
-                            );
-                            #inner
-                        },
-                    )
-                }
-            });
-
-            let mut where_async = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));
-
-            where_async
-                .predicates
-                .push(parse_quote!( #scalar: Send + Sync ));
-            where_async.predicates.push(parse_quote!(Self: Sync));
-
-            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
-                    #where_async
-                {
-                    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>>
-                        where #scalar: Send + Sync,
-                    {
-                        use ::juniper::futures::future;
-                        use ::juniper::GraphQLType;
-                        match field {
-                            #( #resolve_matches_async )*
-                            _ => {
-                                panic!("Field {} not found on type {:?}",
-                                    field,
-                                    <Self as ::juniper::GraphQLType<#scalar>>::name(info)
-                                );
-                            }
-                        }
-                    }
-                }
-
-                #as_dyn_value
-            )
-        };
-
-        let marks = self.fields.iter().map(|field| {
-            let field_marks = field.args.iter().map(|arg| {
-                let arg_ty = &arg._type;
-                quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }
-            });
-
-            let field_ty = &field._type;
-            let resolved_ty = quote! {
-                <#field_ty as ::juniper::IntoResolvable<
-                    '_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
-                >>::Type
-            };
-
-            quote! {
-                #( #field_marks )*
-                <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
-            }
-        });
-
-        let output = quote!(
-            impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause {
-                fn mark() {
-                    #( #marks )*
-                }
-            }
-
-            impl#impl_generics ::juniper::marker::GraphQLObjectType<#scalar> for #ty #type_generics_tokens #where_clause
-            { }
-
-        impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens
-            #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,
-                {
-                    let fields = [
-                        #( #field_definitions ),*
-                    ];
-                    let meta = registry.build_object_type::<#ty>(info, &fields)
-                        #description
-                        #interfaces;
-                    meta.into_meta()
-                }
-        }
-
-        impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #type_generics_tokens
-            #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)
-                }
-
-                #[allow(unused_variables)]
-                #[allow(unused_mut)]
-                fn resolve_field(
-                    &self,
-                    _info: &(),
-                    field: &str,
-                    args: &::juniper::Arguments<#scalar>,
-                    executor: &::juniper::Executor<Self::Context, #scalar>,
-                ) -> ::juniper::ExecutionResult<#scalar> {
-                    match field {
-                        #( #resolve_matches )*
-                        _ => {
-                            panic!("Field {} not found on type {:?}",
-                                field,
-                                <Self as ::juniper::GraphQLType<#scalar>>::name(_info)
-                            );
-                        }
-                    }
-                }
-
-
-                fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String {
-                    #name.to_string()
-                }
-
-        }
-
-        #resolve_field_async
-        );
-        output
-    }
-
-    pub fn into_subscription_tokens(self) -> TokenStream {
-        let name = &self.name;
-        let ty = &self._type;
-        let context = self
-            .context
-            .as_ref()
-            .map(|ctx| quote!( #ctx ))
-            .unwrap_or_else(|| quote!(()));
-
-        let scalar = self
-            .scalar
-            .as_ref()
-            .map(|s| quote!( #s ))
-            .unwrap_or_else(|| {
-                if self.generic_scalar {
-                    // If generic_scalar is true, we always insert a generic scalar.
-                    // See more comments below.
-                    quote!(__S)
-                } else {
-                    quote!(::juniper::DefaultScalarValue)
-                }
-            });
-
-        let field_definitions = self.fields.iter().map(|field| {
-            let args = field.args.iter().map(|arg| {
-                let arg_type = &arg._type;
-                let arg_name = &arg.name;
-
-                let description = match arg.description.as_ref() {
-                    Some(value) => quote!( .description( #value ) ),
-                    None => quote!(),
-                };
-
-                match arg.default.as_ref() {
-                    Some(value) => quote!(
-                        .argument(
-                            registry.arg_with_default::<#arg_type>(#arg_name, &#value, info)
-                                #description
-                        )
-                    ),
-                    None => quote!(
-                        .argument(
-                            registry.arg::<#arg_type>(#arg_name, info)
-                                #description
-                        )
-                    ),
-                }
-            });
-
-            let description = match field.description.as_ref() {
-                Some(description) => quote!( .description(#description) ),
-                None => quote!(),
-            };
-
-            let deprecation = match field.deprecation.as_ref() {
-                Some(deprecation) => {
-                    if let Some(reason) = deprecation.reason.as_ref() {
-                        quote!( .deprecated(Some(#reason)) )
-                    } else {
-                        quote!( .deprecated(None) )
-                    }
-                }
-                None => quote!(),
-            };
-
-            let field_name = &field.name;
-
-            let type_name = &field._type;
-
-            let _type;
-
-            if field.is_async {
-                _type = quote!(<#type_name as ::juniper::ExtractTypeFromStream<_, #scalar>>::Item);
-            } else {
-                panic!("Synchronous resolvers are not supported. Specify that this function is async: 'async fn foo()'")
-            }
-
-            quote! {
-                registry
-                    .field_convert::<#_type, _, Self::Context>(#field_name, info)
-                    #(#args)*
-                    #description
-                    #deprecation
-            }
-        });
-
-        let description = self
-            .description
-            .as_ref()
-            .map(|description| quote!( .description(#description) ));
-
-        let interfaces = if !self.interfaces.is_empty() {
-            let interfaces_ty = &self.interfaces;
-
-            Some(quote!(
-                .interfaces(&[
-                    #( registry.get_type::<#interfaces_ty>(&()) ,)*
-                ])
-            ))
-        } else {
-            None
-        };
-
-        // Preserve the original type_generics before modification,
-        // since alteration makes them invalid if self.generic_scalar
-        // is specified.
-        let (_, type_generics, _) = self.generics.split_for_impl();
-
-        let mut generics = self.generics.clone();
-
-        if self.scalar.is_none() && self.generic_scalar {
-            // No custom scalar specified, but always generic specified.
-            // Therefore we inject the generic scalar.
-
-            // Insert ScalarValue constraint.
-            generics.params.push(parse_quote!(__S));
-            generics
-                .make_where_clause()
-                .predicates
-                .push(parse_quote!(__S: ::juniper::ScalarValue));
-        }
-
-        let type_generics_tokens = if self.include_type_generics {
-            Some(type_generics)
-        } else {
-            None
-        };
-        let (impl_generics, _, where_clause) = generics.split_for_impl();
-
-        let mut generics_with_send_sync = generics.clone();
-        if self.scalar.is_none() && self.generic_scalar {
-            generics_with_send_sync
-                .make_where_clause()
-                .predicates
-                .push(parse_quote!(__S: Send + Sync));
-        }
-        let (_, _, where_clause_with_send_sync) = generics_with_send_sync.split_for_impl();
-
-        let resolve_matches_async = self.fields.iter().filter(|field| field.is_async).map(
-            |field| {
-                let name = &field.name;
-                let code = &field.resolver_code;
-
-                let _type;
-                if field.is_type_inferred {
-                    _type = quote!();
-                } else {
-                    let _type_name = &field._type;
-                    _type = quote!(: #_type_name);
-                };
-                quote!(
-                    #name => {
-                        ::juniper::futures::FutureExt::boxed(async move {
-                            let res #_type = async { #code }.await;
-                            let res = ::juniper::IntoFieldResult::<_, #scalar>::into_result(res)?;
-                            let executor= executor.as_owned_executor();
-                            let f = res.then(move |res| {
-                                let executor = executor.clone();
-                                let res2: ::juniper::FieldResult<_, #scalar> =
-                                    ::juniper::IntoResolvable::into(res, executor.context());
-                                async move {
-                                    let ex = executor.as_executor();
-                                    match res2 {
-                                        Ok(Some((ctx, r))) => {
-                                            let sub = ex.replaced_context(ctx);
-                                            sub.resolve_with_ctx_async(&(), &r)
-                                                .await
-                                                .map_err(|e| ex.new_error(e))
-                                        }
-                                        Ok(None) => Ok(Value::null()),
-                                        Err(e) => Err(ex.new_error(e)),
-                                    }
-                                }
-                            });
-                            Ok(
-                                ::juniper::Value::Scalar::<
-                                    ::juniper::ValuesStream::<#scalar>
-                                >(Box::pin(f))
-                            )
-                        })
-                    }
-                )
-            },
-        );
-
-        let marks = self.fields.iter().map(|field| {
-            let field_marks = field.args.iter().map(|arg| {
-                let arg_ty = &arg._type;
-                quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }
-            });
-
-            let field_ty = &field._type;
-            let stream_item_ty = quote! {
-                <#field_ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item
-            };
-            let resolved_ty = quote! {
-                <#stream_item_ty as ::juniper::IntoResolvable<
-                    '_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
-                >>::Type
-            };
-
-            quote! {
-                #( #field_marks )*
-                <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
-            }
-        });
-
-        let graphql_implementation = quote!(
-            impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens
-                #where_clause
-            {
-                fn mark() {
-                    #( #marks )*
-                }
-            }
-
-            impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens
-                #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,
-                    {
-                        let fields = [
-                            #( #field_definitions ),*
-                        ];
-                        let meta = registry.build_object_type::<#ty>(info, &fields)
-                            #description
-                            #interfaces;
-                        meta.into_meta()
-                    }
-            }
-
-            impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #type_generics_tokens
-                #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,
-                        _: &(),
-                        _: &str,
-                        _: &::juniper::Arguments<#scalar>,
-                        _: &::juniper::Executor<Self::Context, #scalar>,
-                    ) -> ::juniper::ExecutionResult<#scalar> {
-                        panic!("Called `resolve_field` on subscription object");
-                    }
-
-
-                    fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String {
-                        #name.to_string()
-                    }
-            }
-        );
-
-        let subscription_implementation = quote!(
-            impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #type_generics_tokens
-            #where_clause_with_send_sync
-            {
-                #[allow(unused_variables)]
-                fn resolve_field_into_stream<
-                    's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f,
-                >(
-                    &'s self,
-                    info: &'i Self::TypeInfo,
-                    field_name: &'fi str,
-                    args: ::juniper::Arguments<'args, #scalar>,
-                    executor: &'ref_e ::juniper::Executor<'ref_e, 'e, Self::Context, #scalar>,
-                ) -> std::pin::Pin<Box<
-                        dyn ::juniper::futures::future::Future<
-                            Output = std::result::Result<
-                                ::juniper::Value<::juniper::ValuesStream<'res, #scalar>>,
-                                ::juniper::FieldError<#scalar>
-                            >
-                        > + Send + 'f
-                    >>
-                    where
-                        's: 'f,
-                        'i: 'res,
-                        'fi: 'f,
-                        'e: 'res,
-                        'args: 'f,
-                        'ref_e: 'f,
-                        'res: 'f,
-                {
-                    use ::juniper::Value;
-                    use ::juniper::futures::stream::StreamExt as _;
-
-                    match field_name {
-                            #( #resolve_matches_async )*
-                            _ => {
-                                panic!("Field {} not found on type {}", field_name, "GraphQLSubscriptionValue");
-                            }
-                        }
-                }
-            }
-        );
-
-        quote!(
-            #graphql_implementation
-            #subscription_implementation
-        )
-    }
-
     pub fn into_enum_tokens(self) -> TokenStream {
         let name = &self.name;
         let ty = &self._type;
@@ -1728,11 +978,10 @@ impl GraphQLTypeDefiniton {
 
                 quote!(
                     #field_ident: {
-                        // TODO: investigate the unwraps here, they seem dangerous!
                         match obj.get(#field_name) {
                             #from_input_default
                             Some(ref v) => ::juniper::FromInputValue::from_input_value(v)?,
-                            None => ::juniper::FromInputValue::<#scalar>::from_implicit_null(),
+                            None => ::juniper::FromInputValue::<#scalar>::from_implicit_null()?,
                         }
                     },
                 )
diff --git a/juniper_codegen/src/util/parse_impl.rs b/juniper_codegen/src/util/parse_impl.rs
deleted file mode 100644
index db29d9e16..000000000
--- a/juniper_codegen/src/util/parse_impl.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-//! Parse impl blocks.
-#![allow(clippy::or_fun_call)]
-
-use crate::util::{self, span_container::SpanContainer};
-use proc_macro2::{Ident, TokenStream};
-use quote::quote;
-use syn::{spanned::Spanned, PatType};
-
-pub struct ImplBlock {
-    pub attrs: util::ObjectAttributes,
-    pub target_trait: Option<(String, syn::Path)>,
-    pub target_type: Box<syn::Type>,
-    pub type_ident: syn::Ident,
-    pub generics: syn::Generics,
-    // _impl: syn::ItemImpl,
-    pub methods: Vec<syn::ImplItemMethod>,
-    pub description: Option<String>,
-}
-
-impl ImplBlock {
-    /// Parse a `fn <NAME>() -> <TYPE>` method declaration found in
-    /// objects.
-    pub fn parse_method<
-        F: Fn(
-            &PatType,
-            &Ident,
-            bool,
-        ) -> syn::Result<(TokenStream, util::GraphQLTypeDefinitionFieldArg)>,
-    >(
-        &self,
-        method: &syn::ImplItemMethod,
-        is_self_optional: bool,
-        f: F,
-    ) -> syn::Result<(Vec<TokenStream>, Vec<util::GraphQLTypeDefinitionFieldArg>)> {
-        let mut arguments = method.sig.inputs.iter().peekable();
-
-        // Verify `&self` argument.
-        match arguments.peek() {
-            Some(syn::FnArg::Receiver(rec)) => {
-                let _consume = arguments.next();
-                if rec.reference.is_none() || rec.mutability.is_some() {
-                    return Err(syn::Error::new(
-                        rec.span(),
-                        "invalid argument: did you mean `&self`?",
-                    ));
-                }
-            }
-            _ => {
-                if !is_self_optional {
-                    return Err(syn::Error::new(
-                        method.sig.span(),
-                        "expected a `&self` argument",
-                    ));
-                }
-            }
-        }
-
-        let mut resolve_parts = Vec::new();
-        let mut additional_arguments = Vec::new();
-
-        for arg in arguments {
-            match arg {
-                syn::FnArg::Receiver(_) => {
-                    if !is_self_optional {
-                        return Err(syn::Error::new(
-                            method.sig.ident.span(),
-                            "self receiver must be the first argument",
-                        ));
-                    }
-                }
-                syn::FnArg::Typed(captured) => {
-                    let (arg_ident, is_mut) = match &*captured.pat {
-                        syn::Pat::Ident(ref pat_ident) => {
-                            (&pat_ident.ident, pat_ident.mutability.is_some())
-                        }
-                        _ => {
-                            return Err(syn::Error::new(
-                                captured.pat.span(),
-                                "expected identifier for function argument",
-                            ));
-                        }
-                    };
-                    let context_type = self.attrs.context.as_ref();
-
-                    // Check for executor arguments.
-                    if util::type_is_identifier_ref(&captured.ty, "Executor") {
-                        resolve_parts.push(quote!(let #arg_ident = executor;));
-                    }
-                    // Make sure executor is specified as a reference.
-                    else if util::type_is_identifier(&captured.ty, "Executor") {
-                        return Err(syn::Error::new(
-                            captured.ty.span(),
-                            "to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?"
-                        ));
-                    }
-                    // Check for context arg.
-                    else if context_type
-                        .clone()
-                        .map(|ctx| util::type_is_ref_of(&captured.ty, ctx))
-                        .unwrap_or(false)
-                    {
-                        resolve_parts.push(quote!( let #arg_ident = executor.context(); ));
-                    }
-                    // Make sure the user does not specify the Context
-                    //  without a reference. (&Context)
-                    else if context_type
-                        .clone()
-                        .map(|ctx| ctx.inner() == &*captured.ty)
-                        .unwrap_or(false)
-                    {
-                        return Err(syn::Error::new(
-                            captured.ty.span(),
-                            format!("to access the context, you need to specify the type as a reference.\nDid you mean &{}?", quote!(captured.ty)),
-                        ));
-                    } else {
-                        let (tokens, ty) = f(captured, arg_ident, is_mut)?;
-                        resolve_parts.push(tokens);
-                        additional_arguments.push(ty);
-                    }
-                }
-            }
-        }
-
-        Ok((resolve_parts, additional_arguments))
-    }
-
-    pub fn parse(attr_tokens: TokenStream, body: TokenStream) -> syn::Result<ImplBlock> {
-        let attrs = syn::parse2::<util::ObjectAttributes>(attr_tokens)?;
-        let mut _impl = syn::parse2::<syn::ItemImpl>(body)?;
-
-        let target_trait = match _impl.clone().trait_ {
-            Some((_, path, _)) => {
-                let name = path
-                    .segments
-                    .iter()
-                    .map(|segment| segment.ident.to_string())
-                    .collect::<Vec<_>>()
-                    .join(".");
-                Some((name, path))
-            }
-            None => None,
-        };
-
-        let type_ident = if let Some(ident) = util::name_of_type(&*_impl.self_ty) {
-            ident
-        } else {
-            return Err(syn::Error::new(
-                _impl.self_ty.span(),
-                "could not determine a name for the impl type",
-            ));
-        };
-
-        let target_type = _impl.self_ty.clone();
-
-        let description = attrs
-            .description
-            .clone()
-            .or_else(|| util::get_doc_comment(&_impl.attrs.clone()));
-
-        let mut methods = Vec::new();
-
-        for item in _impl.items {
-            match item {
-                syn::ImplItem::Method(method) => {
-                    methods.push(method);
-                }
-                _ => {
-                    return Err(syn::Error::new(
-                        item.span(),
-                        "only type declarations and methods are allowed",
-                    ));
-                }
-            }
-        }
-
-        Ok(Self {
-            attrs,
-            type_ident,
-            target_trait,
-            target_type,
-            generics: _impl.generics,
-            description: description.map(SpanContainer::into_inner),
-            methods,
-        })
-    }
-}
diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs
index 6ad25029a..f5627596e 100644
--- a/juniper_graphql_ws/src/lib.rs
+++ b/juniper_graphql_ws/src/lib.rs
@@ -642,6 +642,8 @@ mod test {
 
     struct Context(i32);
 
+    impl juniper::Context for Context {}
+
     struct Query;
 
     #[graphql_object(context = Context)]
@@ -657,7 +659,7 @@ mod test {
     #[graphql_subscription(context = Context)]
     impl Subscription {
         /// never never emits anything.
-        async fn never(context: &Context) -> BoxStream<'static, FieldResult<i32>> {
+        async fn never(_context: &Context) -> BoxStream<'static, FieldResult<i32>> {
             tokio::time::sleep(Duration::from_secs(10000))
                 .map(|_| unreachable!())
                 .into_stream()
@@ -676,7 +678,7 @@ mod test {
         }
 
         /// error emits an error once, then never emits anything else.
-        async fn error(context: &Context) -> BoxStream<'static, FieldResult<i32>> {
+        async fn error(_context: &Context) -> BoxStream<'static, FieldResult<i32>> {
             stream::once(future::ready(Err(FieldError::new(
                 "field error",
                 Value::null(),
diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml
index 11f034431..53aa080e6 100644
--- a/juniper_hyper/Cargo.toml
+++ b/juniper_hyper/Cargo.toml
@@ -11,13 +11,13 @@ repository = "https://github.com/graphql-rust/juniper"
 [dependencies]
 futures = "0.3.1"
 juniper = { version = "0.15.7", path = "../juniper", default-features = false }
-hyper = {version = "0.14", features = ["server", "runtime"]}
+hyper = { version = "0.14", features = ["server", "runtime"] }
 serde_json = "1.0"
-tokio = "1"
-url = "2"
+tokio = "1.0"
+url = "2.0"
 
 [dev-dependencies]
 juniper = { version = "0.15.7", path = "../juniper", features = ["expose-test-schema"] }
 pretty_env_logger = "0.4"
 reqwest = { version = "0.11", features = ["blocking", "rustls-tls"] }
-tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
+tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs
index b58bcd7aa..118c64862 100644
--- a/juniper_iron/src/lib.rs
+++ b/juniper_iron/src/lib.rs
@@ -44,9 +44,9 @@ use juniper::{Context, EmptyMutation, EmptySubscription};
 #         Ok(&self.name)
 #     }
 #
-#     fn friends(&self, context: &Database) -> FieldResult<Vec<&User>> {
+#     fn friends<'c>(&self, context: &'c Database) -> FieldResult<Vec<&'c User>> {
 #         Ok(self.friend_ids.iter()
-#             .filter_map(|id| executor.context().users.get(id))
+#             .filter_map(|id| context.users.get(id))
 #             .collect())
 #     }
 # }
@@ -54,7 +54,7 @@ use juniper::{Context, EmptyMutation, EmptySubscription};
 # #[juniper::graphql_object(context = Database, scalar = juniper::DefaultScalarValue)]
 # impl QueryRoot {
 #     fn user(context: &Database, id: String) -> FieldResult<Option<&User>> {
-#         Ok(executor.context().users.get(&id))
+#         Ok(context.users.get(&id))
 #     }
 # }
 
diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs
index 64180d700..4c683e92f 100644
--- a/juniper_warp/src/lib.rs
+++ b/juniper_warp/src/lib.rs
@@ -70,6 +70,7 @@ use warp::{body, filters::BoxedFilter, http, hyper::body::Bytes, query, Filter};
 /// # #[derive(Debug)]
 /// struct AppState(Vec<i64>);
 /// struct ExampleContext(Arc<AppState>, UserId);
+/// # impl juniper::Context for ExampleContext {}
 ///
 /// struct QueryRoot;
 ///


Test patch:
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
index e4e09b66a..597171164 100644
--- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr
+++ b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr
@@ -1,10 +1,10 @@
-error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied
+error[E0277]: the trait bound `ObjA: GraphQLObject<__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`
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA`
    |
-   = note: required by `juniper::marker::GraphQLObjectType::mark`
+   = note: required by `juniper::GraphQLObject::mark`
    = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied
diff --git a/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs
new file mode 100644
index 000000000..b85c97216
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs
@@ -0,0 +1,12 @@
+use juniper::graphql_object;
+
+struct Obj;
+
+#[graphql_object]
+impl Obj {
+    fn id(&self, __num: i32) -> &str {
+        "funA"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr b/integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr
similarity index 64%
rename from integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr
rename to integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr
index 40eea80dc..433c024e0 100644
--- a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr
+++ b/integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr
@@ -1,7 +1,7 @@
 error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
- --> $DIR/impl_no_argument_underscore.rs:5:29
+ --> $DIR/argument_double_underscored.rs:7:18
   |
-5 |     #[graphql(arguments(arg(name = "__arg")))]
-  |                             ^^^^
+7 |     fn id(&self, __num: i32) -> &str {
+  |                  ^^^^^
   |
   = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs
new file mode 100644
index 000000000..73dce0de0
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs
@@ -0,0 +1,17 @@
+use juniper::{graphql_object, GraphQLObject};
+
+#[derive(GraphQLObject)]
+struct ObjA {
+    test: String,
+}
+
+struct ObjB;
+
+#[graphql_object]
+impl ObjB {
+    fn id(&self, obj: ObjA) -> &str {
+        "funA"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr
new file mode 100644
index 000000000..1a054e5ef
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/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:10:1
+   |
+10 | #[graphql_object]
+   | ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
+   |
+   = note: required by `juniper::marker::IsInputType::mark`
+   = note: this error originates in the attribute macro `graphql_object` (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:10:1
+   |
+10 | #[graphql_object]
+   | ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
+   |
+   = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs
new file mode 100644
index 000000000..fab39ef73
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs
@@ -0,0 +1,12 @@
+use juniper::graphql_object;
+
+struct ObjA;
+
+#[graphql_object]
+impl ObjA {
+    fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool {
+        input[0]
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr
new file mode 100644
index 000000000..83128b5c8
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr
@@ -0,0 +1,12 @@
+error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
+ --> $DIR/argument_wrong_default_array.rs:5:1
+  |
+5 | #[graphql_object]
+  | ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
+  |
+  = help: the following implementations were found:
+            <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
+            <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
+            <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
+  = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
+  = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs
new file mode 100644
index 000000000..ff854d0a7
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs
@@ -0,0 +1,12 @@
+use juniper::graphql_object;
+
+struct ObjA;
+
+#[graphql_object]
+impl Character for ObjA {
+    fn __id(&self) -> &str {
+        "funA"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr
new file mode 100644
index 000000000..cd1849d85
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr
@@ -0,0 +1,7 @@
+error: #[graphql_object] attribute is applicable to non-trait `impl` blocks only
+ --> $DIR/attr_field_double_underscored.rs:5:1
+  |
+5 | #[graphql_object]
+  | ^^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs
new file mode 100644
index 000000000..c5d748b51
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs
@@ -0,0 +1,17 @@
+use juniper::{graphql_object, GraphQLInputObject};
+
+#[derive(GraphQLInputObject)]
+struct ObjB {
+    id: i32,
+}
+
+struct ObjA;
+
+#[graphql_object]
+impl ObjA {
+    fn id(&self) -> ObjB {
+        ObjB { id: 34 }
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr
new file mode 100644
index 000000000..9bc4c6bda
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr
@@ -0,0 +1,8 @@
+error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
+  --> $DIR/attr_field_non_output_return_type.rs:10:1
+   |
+10 | #[graphql_object]
+   | ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
+   |
+   = note: required by `juniper::marker::IsOutputType::mark`
+   = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs
new file mode 100644
index 000000000..4fe14664c
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs
@@ -0,0 +1,17 @@
+use juniper::graphql_object;
+
+struct ObjA;
+
+#[graphql_object]
+impl ObjA {
+    fn id(&self) -> &str {
+        "funA"
+    }
+
+    #[graphql(name = "id")]
+    fn id2(&self) -> &str {
+        "funB"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr
new file mode 100644
index 000000000..468e4cde5
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr
@@ -0,0 +1,7 @@
+error: GraphQL object must have a different name for each field
+ --> $DIR/attr_fields_duplicate.rs:6:6
+  |
+6 | impl ObjA {
+  |      ^^^^
+  |
+  = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs
new file mode 100644
index 000000000..37ec8ba70
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs
@@ -0,0 +1,12 @@
+use juniper::graphql_object;
+
+struct __Obj;
+
+#[graphql_object]
+impl __Obj {
+    fn id(&self) -> &str {
+        "funA"
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_no_underscore.stderr b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr
similarity index 72%
rename from integration_tests/codegen_fail/fail/object/impl_no_underscore.stderr
rename to integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr
index b7e531e5c..e7bedddbf 100644
--- a/integration_tests/codegen_fail/fail/object/impl_no_underscore.stderr
+++ b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr
@@ -1,7 +1,7 @@
 error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
- --> $DIR/impl_no_underscore.rs:5:15
+ --> $DIR/attr_name_double_underscored.rs:6:6
   |
-5 |     #[graphql(name = "__test")]
-  |               ^^^^
+6 | impl __Obj {
+  |      ^^^^^
   |
   = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/object/attr_no_fields.rs b/integration_tests/codegen_fail/fail/object/attr_no_fields.rs
new file mode 100644
index 000000000..b36c58697
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_no_fields.rs
@@ -0,0 +1,8 @@
+use juniper::graphql_object;
+
+struct Obj;
+
+#[graphql_object]
+impl Obj {}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/attr_no_fields.stderr b/integration_tests/codegen_fail/fail/object/attr_no_fields.stderr
new file mode 100644
index 000000000..f783cc91a
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_no_fields.stderr
@@ -0,0 +1,7 @@
+error: GraphQL object must have at least one field
+ --> $DIR/attr_no_fields.rs:6:6
+  |
+6 | impl Obj {}
+  |      ^^^
+  |
+  = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs b/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs
new file mode 100644
index 000000000..92ba57ec2
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs
@@ -0,0 +1,6 @@
+use juniper::graphql_object;
+
+#[graphql_object]
+enum Character {}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr b/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr
new file mode 100644
index 000000000..cf9141485
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr
@@ -0,0 +1,7 @@
+error: #[graphql_object] attribute is applicable to non-trait `impl` blocks only
+ --> $DIR/attr_wrong_item.rs:3:1
+  |
+3 | #[graphql_object]
+  | ^^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs
new file mode 100644
index 000000000..286383edf
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs
@@ -0,0 +1,8 @@
+use juniper::GraphQLObject;
+
+#[derive(GraphQLObject)]
+struct Object {
+    __test: String,
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_no_underscore.stderr b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr
similarity index 71%
rename from integration_tests/codegen_fail/fail/object/derive_no_underscore.stderr
rename to integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr
index eb7eb67b5..a6f3cb8db 100644
--- a/integration_tests/codegen_fail/fail/object/derive_no_underscore.stderr
+++ b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr
@@ -1,7 +1,7 @@
 error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
- --> $DIR/derive_no_underscore.rs:3:15
+ --> $DIR/derive_field_double_underscored.rs:5:5
   |
-3 |     #[graphql(name = "__test")]
-  |               ^^^^
+5 |     __test: String,
+  |     ^^^^^^
   |
   = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs
new file mode 100644
index 000000000..7197cd595
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs
@@ -0,0 +1,13 @@
+use juniper::{GraphQLInputObject, GraphQLObject};
+
+#[derive(GraphQLInputObject)]
+struct ObjB {
+    id: i32,
+}
+
+#[derive(GraphQLObject)]
+struct ObjA {
+    id: ObjB,
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr
new file mode 100644
index 000000000..d112debd2
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr
@@ -0,0 +1,8 @@
+error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
+ --> $DIR/derive_field_non_output_return_type.rs:8:10
+  |
+8 | #[derive(GraphQLObject)]
+  |          ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
+  |
+  = note: required by `juniper::marker::IsOutputType::mark`
+  = note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs
new file mode 100644
index 000000000..cd73e1f3d
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs
@@ -0,0 +1,10 @@
+use juniper::GraphQLObject;
+
+#[derive(GraphQLObject)]
+struct ObjA {
+    id: String,
+    #[graphql(name = "id")]
+    id2: String,
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr
new file mode 100644
index 000000000..74004561b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr
@@ -0,0 +1,11 @@
+error: GraphQL object must have a different name for each field
+ --> $DIR/derive_fields_duplicate.rs:4:1
+  |
+4 | / struct ObjA {
+5 | |     id: String,
+6 | |     #[graphql(name = "id")]
+7 | |     id2: String,
+8 | | }
+  | |_^
+  |
+  = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_unique.rs b/integration_tests/codegen_fail/fail/object/derive_fields_unique.rs
deleted file mode 100644
index ee5afa112..000000000
--- a/integration_tests/codegen_fail/fail/object/derive_fields_unique.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-struct Object {
-    test: String,
-    #[graphql(name = "test")]
-    test2: String,
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr b/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr
deleted file mode 100644
index 999b01168..000000000
--- a/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr
+++ /dev/null
@@ -1,9 +0,0 @@
-error: GraphQL object does not allow fields with the same name
- --> $DIR/derive_fields_unique.rs:4:5
-  |
-4 | /     #[graphql(name = "test")]
-5 | |     test2: String,
-  | |_________________^
-  |
-  = help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
-  = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled b/integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled
deleted file mode 100644
index 0e0122190..000000000
--- a/integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled
+++ /dev/null
@@ -1,12 +0,0 @@
-// FIXME: enable this if interfaces are supported
-#[derive(juniper::GraphQLInputObject)]
-struct ObjectA {
-    test: String,
-}
-
-#[derive(juniper::GraphQLObject)]
-struct Object {
-    field: ObjectA,
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs b/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs
new file mode 100644
index 000000000..209b3f37b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs
@@ -0,0 +1,8 @@
+use juniper::GraphQLObject;
+
+#[derive(GraphQLObject)]
+struct __Obj {
+    id: String,
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr b/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr
new file mode 100644
index 000000000..11eafcef6
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_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/derive_name_double_underscored.rs:4:8
+  |
+4 | struct __Obj {
+  |        ^^^^^
+  |
+  = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.rs b/integration_tests/codegen_fail/fail/object/derive_no_fields.rs
index cd72632ae..27795d55a 100644
--- a/integration_tests/codegen_fail/fail/object/derive_no_fields.rs
+++ b/integration_tests/codegen_fail/fail/object/derive_no_fields.rs
@@ -1,4 +1,6 @@
-#[derive(juniper::GraphQLObject)]
-struct Object {}
+use juniper::GraphQLObject;
+
+#[derive(GraphQLObject)]
+struct Obj {}
 
 fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr b/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr
index 183cd4304..1975c3611 100644
--- a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr
+++ b/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr
@@ -1,7 +1,7 @@
-error: GraphQL object expects at least one field
- --> $DIR/derive_no_fields.rs:2:1
+error: GraphQL object must have at least one field
+ --> $DIR/derive_no_fields.rs:4:1
   |
-2 | struct Object {}
-  | ^^^^^^^^^^^^^^^^
+4 | struct Obj {}
+  | ^^^^^^^^^^^^^
   |
   = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/derive_no_underscore.rs b/integration_tests/codegen_fail/fail/object/derive_no_underscore.rs
deleted file mode 100644
index 9b270b546..000000000
--- a/integration_tests/codegen_fail/fail/object/derive_no_underscore.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-struct Object {
-    #[graphql(name = "__test")]
-    test: String,
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs b/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs
new file mode 100644
index 000000000..8318256fc
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs
@@ -0,0 +1,6 @@
+use juniper::GraphQLObject;
+
+#[derive(GraphQLObject)]
+enum Character {}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr b/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr
new file mode 100644
index 000000000..50680bec9
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr
@@ -0,0 +1,5 @@
+error: GraphQL object can only be derived for structs
+ --> $DIR/derive_wrong_item.rs:4:1
+  |
+4 | enum Character {}
+  | ^^^^^^^^^^^^^^^^^
diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs
deleted file mode 100644
index a584a16f3..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-struct Obj {
-    field: String,
-}
-
-struct Object {}
-
-#[juniper::graphql_object]
-impl Object {
-    fn test(&self, test: Obj) -> String {
-        String::new()
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr
deleted file mode 100644
index 384686082..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr
+++ /dev/null
@@ -1,29 +0,0 @@
-error[E0277]: the trait bound `Obj: IsInputType<__S>` is not satisfied
- --> $DIR/impl_argument_no_object.rs:8:1
-  |
-8 | #[juniper::graphql_object]
-  | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `Obj`
-  |
-  = note: required by `juniper::marker::IsInputType::mark`
-  = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error[E0277]: the trait bound `Obj: FromInputValue<__S>` is not satisfied
- --> $DIR/impl_argument_no_object.rs:8:1
-  |
-8 | #[juniper::graphql_object]
-  | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `Obj`
-  |
-  = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error[E0277]: the trait bound `Obj: FromInputValue` is not satisfied
-   --> $DIR/impl_argument_no_object.rs:8:1
-    |
-8   | #[juniper::graphql_object]
-    | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue` is not implemented for `Obj`
-    |
-   ::: $WORKSPACE/juniper/src/ast.rs
-    |
-    | pub trait FromInputValue<S = DefaultScalarValue>: Sized {
-    | ------------------------------------------------------- required by this bound in `FromInputValue`
-    |
-    = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs
deleted file mode 100644
index 740400c4d..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-struct Object;
-
-#[juniper::graphql_object]
-impl Object {
-    #[graphql(arguments(input(default = [true, false, false])))]
-    fn wrong(input: [bool; 2]) -> bool {
-        input[0]
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr
deleted file mode 100644
index eb85ff256..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr
+++ /dev/null
@@ -1,7 +0,0 @@
-error[E0308]: mismatched types
- --> $DIR/impl_argument_wrong_default_array.rs:3:1
-  |
-3 | #[juniper::graphql_object]
-  | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 2 elements, found one with 3 elements
-  |
-  = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/object/impl_fields_unique.rs b/integration_tests/codegen_fail/fail/object/impl_fields_unique.rs
deleted file mode 100644
index c89fc9aa7..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_fields_unique.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-struct Object {}
-
-#[juniper::graphql_object]
-impl Object {
-    fn test(&self) -> String {
-        String::new()
-    }
-
-    fn test(&self) -> String {
-        String::new()
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr b/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr
deleted file mode 100644
index 49649a2ac..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr
+++ /dev/null
@@ -1,10 +0,0 @@
-error: GraphQL object does not allow fields with the same name
-  --> $DIR/impl_fields_unique.rs:9:5
-   |
-9  | /     fn test(&self) -> String {
-10 | |         String::new()
-11 | |     }
-   | |_____^
-   |
-   = help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
-   = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled b/integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled
deleted file mode 100644
index e7661e402..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled
+++ /dev/null
@@ -1,19 +0,0 @@
-// FIXME: enable this if interfaces are supported
-#[derive(juniper::GraphQLInputObject)]
-#[graphql(scalar = juniper::DefaultScalarValue)]
-struct Obj {
-    field: String,
-}
-
-struct Object {}
-
-#[juniper::graphql_object]
-impl Object {
-    fn test(&self) -> Obj {
-        Obj {
-            field: String::new(),
-        }
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs b/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs
deleted file mode 100644
index 02fec848e..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-struct Object {}
-
-#[juniper::graphql_object]
-impl Object {
-    #[graphql(arguments(arg(name = "__arg")))]
-    fn test(&self, arg: String) -> String {
-        arg
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_no_fields.rs b/integration_tests/codegen_fail/fail/object/impl_no_fields.rs
deleted file mode 100644
index aa863c7cd..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_no_fields.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-struct Object {}
-
-#[juniper::graphql_object]
-impl Object {}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr b/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr
deleted file mode 100644
index 798a7db20..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr
+++ /dev/null
@@ -1,7 +0,0 @@
-error: GraphQL object expects at least one field
- --> $DIR/impl_no_fields.rs:4:1
-  |
-4 | impl Object {}
-  | ^^^^^^^^^^^^^^
-  |
-  = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/object/impl_no_underscore.rs b/integration_tests/codegen_fail/fail/object/impl_no_underscore.rs
deleted file mode 100644
index 5e9ddbfac..000000000
--- a/integration_tests/codegen_fail/fail/object/impl_no_underscore.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-struct Object {}
-
-#[juniper::graphql_object]
-impl Object {
-    #[graphql(name = "__test")]
-    fn test(&self) -> String {
-        String::new()
-    }
-}
-
-fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs
new file mode 100644
index 000000000..fe01fc69e
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs
@@ -0,0 +1,16 @@
+use std::pin::Pin;
+
+use juniper::graphql_subscription;
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+struct Obj;
+
+#[graphql_subscription]
+impl Obj {
+    async fn id(&self, __num: i32) -> Stream<'static, &'static str> {
+        Box::pin(stream::once(future::ready("funA")))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr
new file mode 100644
index 000000000..97d9d6df1
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/argument_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/argument_double_underscored.rs:11:24
+   |
+11 |     async fn id(&self, __num: i32) -> Stream<'static, &'static str> {
+   |                        ^^^^^
+   |
+   = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs
new file mode 100644
index 000000000..90b24bb21
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs
@@ -0,0 +1,22 @@
+use std::pin::Pin;
+
+use futures::{future, stream};
+use juniper::{graphql_subscription, GraphQLObject};
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+#[derive(GraphQLObject)]
+struct ObjA {
+    test: String,
+}
+
+struct ObjB;
+
+#[graphql_subscription]
+impl ObjB {
+    async fn id(&self, obj: ObjA) -> Stream<'static, &'static str> {
+        Box::pin(stream::once(future::ready("funA")))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr
new file mode 100644
index 000000000..87d4a9ee7
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/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:15:1
+   |
+15 | #[graphql_subscription]
+   | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
+   |
+   = note: required by `juniper::marker::IsInputType::mark`
+   = note: this error originates in the attribute macro `graphql_subscription` (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:15:1
+   |
+15 | #[graphql_subscription]
+   | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
+   |
+   = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs
new file mode 100644
index 000000000..c0ec68639
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs
@@ -0,0 +1,20 @@
+use std::pin::Pin;
+
+use futures::{future, stream};
+use juniper::graphql_subscription;
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+struct ObjA;
+
+#[graphql_subscription]
+impl ObjA {
+    async fn wrong(
+        &self,
+        #[graphql(default = [true, false, false])] input: [bool; 2],
+    ) -> Stream<'static, bool> {
+        Box::pin(stream::once(future::ready(input[0])))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr
new file mode 100644
index 000000000..56b033cbb
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr
@@ -0,0 +1,12 @@
+error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
+  --> $DIR/argument_wrong_default_array.rs:10:1
+   |
+10 | #[graphql_subscription]
+   | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
+   |
+   = help: the following implementations were found:
+             <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
+             <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
+             <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
+   = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
+   = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs
new file mode 100644
index 000000000..686dccc27
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs
@@ -0,0 +1,16 @@
+use std::pin::Pin;
+
+use juniper::graphql_subscription;
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+struct ObjA;
+
+#[graphql_subscription]
+impl Character for ObjA {
+    async fn __id() -> Stream<'static, &'static str> {
+        Box::pin(stream::once(future::ready("funA")))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr
new file mode 100644
index 000000000..1f5e519af
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr
@@ -0,0 +1,7 @@
+error: #[graphql_subscription] attribute is applicable to non-trait `impl` blocks only
+ --> $DIR/field_double_underscored.rs:9:1
+  |
+9 | #[graphql_subscription]
+  | ^^^^^^^^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs
new file mode 100644
index 000000000..8cc918542
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs
@@ -0,0 +1,22 @@
+use std::pin::Pin;
+
+use futures::{future, stream};
+use juniper::{graphql_subscription, GraphQLInputObject};
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+#[derive(GraphQLInputObject)]
+struct ObjB {
+    id: i32,
+}
+
+struct ObjA;
+
+#[graphql_subscription]
+impl ObjA {
+    async fn id(&self) -> Stream<'static, ObjB> {
+        Box::pin(stream::once(future::ready(ObjB { id: 34 })))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr
new file mode 100644
index 000000000..84d4cf63b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/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:15:1
+   |
+15 | #[graphql_subscription]
+   | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
+   |
+   = note: required by `juniper::marker::IsOutputType::mark`
+   = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/subscription/field_not_async.rs b/integration_tests/codegen_fail/fail/subscription/field_not_async.rs
new file mode 100644
index 000000000..f9cfb5b53
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/field_not_async.rs
@@ -0,0 +1,16 @@
+use std::pin::Pin;
+
+use juniper::graphql_subscription;
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+struct ObjA;
+
+#[graphql_subscription]
+impl ObjA {
+    fn id(&self) -> Stream<'static, bool> {
+        Box::pin(stream::once(future::ready(true)))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr b/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr
new file mode 100644
index 000000000..3e5209c8e
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr
@@ -0,0 +1,8 @@
+error: GraphQL object synchronous resolvers are not supported
+  --> $DIR/field_not_async.rs:11:5
+   |
+11 |     fn id(&self) -> Stream<'static, bool> {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: https://spec.graphql.org/June2018/#sec-Objects
+   = note: Specify that this function is async: `async fn foo()`
diff --git a/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs
new file mode 100644
index 000000000..22ccc9778
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs
@@ -0,0 +1,21 @@
+use std::pin::Pin;
+
+use juniper::graphql_subscription;
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+struct ObjA;
+
+#[graphql_subscription]
+impl ObjA {
+    async fn id(&self) -> Stream<'static, &'static str> {
+        Box::pin(stream::once(future::ready("funA")))
+    }
+
+    #[graphql(name = "id")]
+    async fn id2(&self) -> Stream<'static, &'static str> {
+        Box::pin(stream::once(future::ready("funB")))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr
new file mode 100644
index 000000000..d9156450c
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr
@@ -0,0 +1,7 @@
+error: GraphQL object must have a different name for each field
+  --> $DIR/fields_duplicate.rs:10:6
+   |
+10 | impl ObjA {
+   |      ^^^^
+   |
+   = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs
new file mode 100644
index 000000000..d9911148b
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs
@@ -0,0 +1,16 @@
+use std::pin::Pin;
+
+use juniper::graphql_subscription;
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+struct __Obj;
+
+#[graphql_subscription]
+impl __Obj {
+    fn id(&self) -> Stream<'static, &'static str> {
+        Box::pin(stream::once(future::ready("funA")))
+    }
+}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr
new file mode 100644
index 000000000..ac1081e35
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/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:10:6
+   |
+10 | impl __Obj {
+   |      ^^^^^
+   |
+   = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/integration_tests/codegen_fail/fail/subscription/no_fields.rs b/integration_tests/codegen_fail/fail/subscription/no_fields.rs
new file mode 100644
index 000000000..0c199c13c
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/no_fields.rs
@@ -0,0 +1,8 @@
+use juniper::graphql_subscription;
+
+struct Obj;
+
+#[graphql_subscription]
+impl Obj {}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/no_fields.stderr b/integration_tests/codegen_fail/fail/subscription/no_fields.stderr
new file mode 100644
index 000000000..93b5487f5
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/no_fields.stderr
@@ -0,0 +1,7 @@
+error: GraphQL object must have at least one field
+ --> $DIR/no_fields.rs:6:6
+  |
+6 | impl Obj {}
+  |      ^^^
+  |
+  = note: https://spec.graphql.org/June2018/#sec-Objects
diff --git a/integration_tests/codegen_fail/fail/subscription/wrong_item.rs b/integration_tests/codegen_fail/fail/subscription/wrong_item.rs
new file mode 100644
index 000000000..3ab174c6f
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/wrong_item.rs
@@ -0,0 +1,6 @@
+use juniper::graphql_subscription;
+
+#[graphql_subscription]
+enum Character {}
+
+fn main() {}
diff --git a/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr b/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr
new file mode 100644
index 000000000..d149b56be
--- /dev/null
+++ b/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr
@@ -0,0 +1,7 @@
+error: #[graphql_subscription] attribute is applicable to non-trait `impl` blocks only
+ --> $DIR/wrong_item.rs:3:1
+  |
+3 | #[graphql_subscription]
+  | ^^^^^^^^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr
index 3cb23c268..fc7f13fa1 100644
--- a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr
+++ b/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr
@@ -1,8 +1,8 @@
-error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied
+error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
  --> $DIR/enum_non_object_variant.rs:9:10
   |
 9 | #[derive(GraphQLUnion)]
-  |          ^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test`
+  |          ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
   |
-  = note: required by `juniper::marker::GraphQLObjectType::mark`
+  = note: required by `juniper::GraphQLObject::mark`
   = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr
index 8981bacfc..200aeecdf 100644
--- a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr
+++ b/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr
@@ -1,8 +1,8 @@
-error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied
+error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
  --> $DIR/struct_non_object_variant.rs:9:10
   |
 9 | #[derive(GraphQLUnion)]
-  |          ^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test`
+  |          ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
   |
-  = note: required by `juniper::marker::GraphQLObjectType::mark`
+  = note: required by `juniper::GraphQLObject::mark`
   = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr
index 95cb302b5..e9ac7cdb7 100644
--- a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr
+++ b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr
@@ -1,8 +1,8 @@
-error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied
+error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
  --> $DIR/trait_non_object_variant.rs:9:1
   |
 9 | #[graphql_union]
-  | ^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test`
+  | ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
   |
-  = note: required by `juniper::marker::GraphQLObjectType::mark`
+  = note: required by `juniper::GraphQLObject::mark`
   = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/integration_tests/juniper_tests/src/array.rs b/integration_tests/juniper_tests/src/array.rs
index 43cbd8ea4..aa1606511 100644
--- a/integration_tests/juniper_tests/src/array.rs
+++ b/integration_tests/juniper_tests/src/array.rs
@@ -145,8 +145,7 @@ mod as_input_argument {
             input[0]
         }
 
-        #[graphql(arguments(input(default = [true, false, false])))]
-        fn third(input: [bool; 3]) -> bool {
+        fn third(#[graphql(default = [true, false, false])] input: [bool; 3]) -> bool {
             input[2]
         }
     }
diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs
index 47329b9b0..26b0f2e1f 100644
--- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs
+++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs
@@ -69,7 +69,7 @@ fn test_derived_enum() {
     let meta = SomeEnum::meta(&(), &mut registry);
 
     assert_eq!(meta.name(), Some("Some"));
-    assert_eq!(meta.description(), Some(&"enum descr".to_string()));
+    assert_eq!(meta.description(), Some("enum descr"));
 
     // Test no rename variant.
     assert_eq!(
@@ -102,24 +102,21 @@ fn test_derived_enum() {
 fn test_doc_comment() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = DocEnum::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"Enum doc.".to_string()));
+    assert_eq!(meta.description(), Some("Enum doc."));
 }
 
 #[test]
 fn test_multi_doc_comment() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = MultiDocEnum::meta(&(), &mut registry);
-    assert_eq!(
-        meta.description(),
-        Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
-    );
+    assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4."));
 }
 
 #[test]
 fn test_doc_comment_override() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = OverrideDocEnum::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"enum override".to_string()));
+    assert_eq!(meta.description(), Some("enum override"));
 }
 
 fn test_context<T>(_t: T)
diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs
index cc1595f5e..9b4c72909 100644
--- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs
+++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs
@@ -115,7 +115,7 @@ fn test_derived_input_object() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = Input::meta(&(), &mut registry);
     assert_eq!(meta.name(), Some("MyInput"));
-    assert_eq!(meta.description(), Some(&"input descr".to_string()));
+    assert_eq!(meta.description(), Some("input descr"));
 
     // Test default value injection.
 
@@ -173,22 +173,19 @@ fn test_derived_input_object() {
 fn test_doc_comment() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = DocComment::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"Object comment.".to_string()));
+    assert_eq!(meta.description(), Some("Object comment."));
 }
 
 #[test]
 fn test_multi_doc_comment() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = MultiDocComment::meta(&(), &mut registry);
-    assert_eq!(
-        meta.description(),
-        Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
-    );
+    assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4."));
 }
 
 #[test]
 fn test_doc_comment_override() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = OverrideDocComment::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"obj override".to_string()));
+    assert_eq!(meta.description(), Some("obj override"));
 }
diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs
deleted file mode 100644
index 2a5336aba..000000000
--- a/integration_tests/juniper_tests/src/codegen/derive_object.rs
+++ /dev/null
@@ -1,502 +0,0 @@
-use fnv::FnvHashMap;
-use juniper::{
-    execute, graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject,
-    GraphQLType, Object, Registry, RootNode, Value, Variables,
-};
-
-#[derive(GraphQLObject, Debug, PartialEq)]
-#[graphql(name = "MyObj", description = "obj descr")]
-struct Obj {
-    regular_field: bool,
-    #[graphql(
-        name = "renamedField",
-        description = "descr",
-        deprecated = "field deprecation"
-    )]
-    c: i32,
-}
-
-#[derive(GraphQLObject, Debug, PartialEq)]
-struct Nested {
-    obj: Obj,
-}
-
-/// Object comment.
-#[derive(GraphQLObject, Debug, PartialEq)]
-struct DocComment {
-    /// Field comment.
-    regular_field: bool,
-}
-
-/// Doc 1.\
-/// Doc 2.
-///
-/// Doc 4.
-#[derive(GraphQLObject, Debug, PartialEq)]
-struct MultiDocComment {
-    /// Field 1.
-    /// Field 2.
-    regular_field: bool,
-}
-
-/// This is not used as the description.
-#[derive(GraphQLObject, Debug, PartialEq)]
-#[graphql(description = "obj override")]
-struct OverrideDocComment {
-    /// This is not used as the description.
-    #[graphql(description = "field override")]
-    regular_field: bool,
-}
-
-#[derive(GraphQLObject, Debug, PartialEq)]
-struct WithLifetime<'a> {
-    regular_field: &'a i32,
-}
-
-#[derive(GraphQLObject, Debug, PartialEq)]
-struct SkippedFieldObj {
-    regular_field: bool,
-    #[graphql(skip)]
-    skipped: i32,
-}
-
-#[derive(GraphQLObject, Debug, PartialEq)]
-#[graphql(rename = "none")]
-struct NoRenameObj {
-    one_field: bool,
-    another_field: i32,
-}
-
-struct Context;
-impl juniper::Context for Context {}
-
-#[derive(GraphQLObject, Debug)]
-#[graphql(context = Context)]
-struct WithCustomContext {
-    a: bool,
-}
-
-struct Query;
-
-#[graphql_object]
-impl Query {
-    fn obj() -> Obj {
-        Obj {
-            regular_field: true,
-            c: 22,
-        }
-    }
-
-    fn nested() -> Nested {
-        Nested {
-            obj: Obj {
-                regular_field: false,
-                c: 333,
-            },
-        }
-    }
-
-    fn doc() -> DocComment {
-        DocComment {
-            regular_field: true,
-        }
-    }
-
-    fn multi_doc() -> MultiDocComment {
-        MultiDocComment {
-            regular_field: true,
-        }
-    }
-
-    fn override_doc() -> OverrideDocComment {
-        OverrideDocComment {
-            regular_field: true,
-        }
-    }
-
-    fn skipped_field_obj() -> SkippedFieldObj {
-        SkippedFieldObj {
-            regular_field: false,
-            skipped: 42,
-        }
-    }
-
-    fn no_rename_obj() -> NoRenameObj {
-        NoRenameObj {
-            one_field: true,
-            another_field: 146,
-        }
-    }
-}
-
-struct NoRenameQuery;
-
-#[graphql_object(rename = "none")]
-impl NoRenameQuery {
-    fn obj() -> Obj {
-        Obj {
-            regular_field: false,
-            c: 22,
-        }
-    }
-
-    fn no_rename_obj() -> NoRenameObj {
-        NoRenameObj {
-            one_field: true,
-            another_field: 146,
-        }
-    }
-}
-
-#[tokio::test]
-async fn test_doc_comment_simple() {
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = DocComment::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"Object comment.".to_string()));
-
-    check_descriptions(
-        "DocComment",
-        &Value::scalar("Object comment."),
-        "regularField",
-        &Value::scalar("Field comment."),
-    )
-    .await;
-}
-
-#[tokio::test]
-async fn test_multi_doc_comment() {
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = MultiDocComment::meta(&(), &mut registry);
-    assert_eq!(
-        meta.description(),
-        Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
-    );
-
-    check_descriptions(
-        "MultiDocComment",
-        &Value::scalar("Doc 1. Doc 2.\n\nDoc 4."),
-        "regularField",
-        &Value::scalar("Field 1.\nField 2."),
-    )
-    .await;
-}
-
-#[tokio::test]
-async fn test_doc_comment_override() {
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = OverrideDocComment::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"obj override".to_string()));
-
-    check_descriptions(
-        "OverrideDocComment",
-        &Value::scalar("obj override"),
-        "regularField",
-        &Value::scalar("field override"),
-    )
-    .await;
-}
-
-#[tokio::test]
-async fn test_derived_object() {
-    assert_eq!(
-        <Obj as GraphQLType<DefaultScalarValue>>::name(&()),
-        Some("MyObj")
-    );
-
-    // Verify meta info.
-    let mut registry: Registry = Registry::new(FnvHashMap::default());
-    let meta = Obj::meta(&(), &mut registry);
-
-    assert_eq!(meta.name(), Some("MyObj"));
-    assert_eq!(meta.description(), Some(&"obj descr".to_string()));
-
-    let doc = r#"
-        {
-            obj {
-                regularField
-                renamedField
-            }
-        }"#;
-
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::new(), &()).await,
-        Ok((
-            Value::object(
-                vec![(
-                    "obj",
-                    Value::object(
-                        vec![
-                            ("regularField", Value::scalar(true)),
-                            ("renamedField", Value::scalar(22)),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                )]
-                .into_iter()
-                .collect()
-            ),
-            vec![]
-        ))
-    );
-}
-
-#[tokio::test]
-#[should_panic]
-async fn test_cannot_query_skipped_field() {
-    let doc = r#"
-        {
-            skippedFieldObj {
-                skippedField
-            }
-        }"#;
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-    execute(doc, None, &schema, &Variables::new(), &())
-        .await
-        .unwrap();
-}
-
-#[tokio::test]
-async fn test_skipped_field_siblings_unaffected() {
-    let doc = r#"
-        {
-            skippedFieldObj {
-                regularField
-            }
-        }"#;
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-    execute(doc, None, &schema, &Variables::new(), &())
-        .await
-        .unwrap();
-}
-
-#[tokio::test]
-async fn test_derived_object_nested() {
-    let doc = r#"
-        {
-            nested {
-                obj {
-                    regularField
-                    renamedField
-                }
-            }
-        }"#;
-
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::new(), &()).await,
-        Ok((
-            Value::object(
-                vec![(
-                    "nested",
-                    Value::object(
-                        vec![(
-                            "obj",
-                            Value::object(
-                                vec![
-                                    ("regularField", Value::scalar(false)),
-                                    ("renamedField", Value::scalar(333)),
-                                ]
-                                .into_iter()
-                                .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                )]
-                .into_iter()
-                .collect()
-            ),
-            vec![]
-        ))
-    );
-}
-
-#[tokio::test]
-async fn test_no_rename_root() {
-    let doc = r#"
-        {
-            no_rename_obj {
-                one_field
-                another_field
-            }
-
-            obj {
-                regularField
-            }
-        }"#;
-
-    let schema = RootNode::new(
-        NoRenameQuery,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::new(), &()).await,
-        Ok((
-            Value::object(
-                vec![
-                    (
-                        "no_rename_obj",
-                        Value::object(
-                            vec![
-                                ("one_field", Value::scalar(true)),
-                                ("another_field", Value::scalar(146)),
-                            ]
-                            .into_iter()
-                            .collect(),
-                        ),
-                    ),
-                    (
-                        "obj",
-                        Value::object(
-                            vec![("regularField", Value::scalar(false)),]
-                                .into_iter()
-                                .collect(),
-                        ),
-                    )
-                ]
-                .into_iter()
-                .collect()
-            ),
-            vec![]
-        ))
-    );
-}
-
-#[tokio::test]
-async fn test_no_rename_obj() {
-    let doc = r#"
-        {
-            noRenameObj {
-                one_field
-                another_field
-            }
-        }"#;
-
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    assert_eq!(
-        execute(doc, None, &schema, &Variables::new(), &()).await,
-        Ok((
-            Value::object(
-                vec![(
-                    "noRenameObj",
-                    Value::object(
-                        vec![
-                            ("one_field", Value::scalar(true)),
-                            ("another_field", Value::scalar(146)),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                )]
-                .into_iter()
-                .collect()
-            ),
-            vec![]
-        ))
-    );
-}
-
-async fn check_descriptions(
-    object_name: &str,
-    object_description: &Value,
-    field_name: &str,
-    field_value: &Value,
-) {
-    let doc = format!(
-        r#"
-    {{
-        __type(name: "{}") {{
-            name,
-            description,
-            fields {{
-                name
-                description
-            }}
-        }}
-    }}
-    "#,
-        object_name
-    );
-    let _result = run_type_info_query(&doc, |(type_info, values)| {
-        assert_eq!(
-            type_info.get_field_value("name"),
-            Some(&Value::scalar(object_name))
-        );
-        assert_eq!(
-            type_info.get_field_value("description"),
-            Some(object_description)
-        );
-        assert!(values.contains(&Value::object(
-            vec![
-                ("name", Value::scalar(field_name)),
-                ("description", field_value.clone()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-async fn run_type_info_query<F>(doc: &str, f: F)
-where
-    F: Fn((&Object<DefaultScalarValue>, &Vec<Value>)) -> (),
-{
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    let (result, errs) = execute(doc, None, &schema, &Variables::new(), &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let fields = type_info
-        .get_field_value("fields")
-        .expect("fields field missing")
-        .as_list_value()
-        .expect("fields not a list");
-
-    f((type_info, fields));
-}
diff --git a/integration_tests/juniper_tests/src/codegen/impl_object.rs b/integration_tests/juniper_tests/src/codegen/impl_object.rs
deleted file mode 100644
index 7a7245ff6..000000000
--- a/integration_tests/juniper_tests/src/codegen/impl_object.rs
+++ /dev/null
@@ -1,139 +0,0 @@
-use juniper::{
-    execute, graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, Object,
-    RootNode, Value, Variables,
-};
-
-pub struct MyObject;
-
-#[graphql_object]
-impl MyObject {
-    #[graphql(arguments(arg(name = "test")))]
-    fn test(&self, arg: String) -> String {
-        arg
-    }
-}
-
-#[tokio::test]
-async fn check_argument_rename() {
-    let doc = format!(
-        r#"
-    {{
-        __type(name: "{}") {{
-            name,
-            description,
-            fields {{
-                name
-                description
-            }}
-        }}
-    }}
-    "#,
-        "MyObject"
-    );
-
-    run_type_info_query(&doc, |(_, values)| {
-        assert_eq!(
-            *values,
-            vec![Value::object(
-                vec![
-                    ("name", Value::scalar("test")),
-                    ("description", Value::null()),
-                ]
-                .into_iter()
-                .collect(),
-            )]
-        );
-    })
-    .await;
-}
-
-async fn run_type_info_query<F>(doc: &str, f: F)
-where
-    F: Fn((&Object<DefaultScalarValue>, &Vec<Value>)) -> (),
-{
-    let schema = RootNode::new(
-        MyObject,
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    let (result, errs) = execute(doc, None, &schema, &Variables::new(), &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let fields = type_info
-        .get_field_value("fields")
-        .expect("fields field missing")
-        .as_list_value()
-        .expect("fields not a list");
-
-    f((type_info, fields));
-}
-
-mod fallible {
-    use juniper::{graphql_object, FieldError};
-
-    struct Obj;
-
-    #[graphql_object]
-    impl Obj {
-        fn test(&self, arg: String) -> Result<String, FieldError> {
-            Ok(arg)
-        }
-    }
-}
-
-mod raw_argument {
-    use juniper::{
-        graphql_object, graphql_value, EmptyMutation, EmptySubscription, RootNode, Variables,
-    };
-
-    struct Obj;
-
-    #[graphql_object]
-    impl Obj {
-        #[graphql(arguments(r#arg(description = "The only argument")))]
-        fn test(&self, arg: String) -> String {
-            arg
-        }
-    }
-
-    #[tokio::test]
-    async fn named_correctly() {
-        let doc = r#"{
-            __type(name: "Obj") {
-                fields {
-                    args {
-                        name
-                    }
-                }
-            }
-        }"#;
-
-        let schema = RootNode::new(
-            Obj,
-            EmptyMutation::<()>::new(),
-            EmptySubscription::<()>::new(),
-        );
-
-        assert_eq!(
-            juniper::execute(&doc, None, &schema, &Variables::new(), &()).await,
-            Ok((
-                graphql_value!({"__type": {"fields": [{"args": [{"name": "arg"}]}]}}),
-                vec![],
-            )),
-        );
-    }
-}
diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs
index b85f4f195..54d2e6e12 100644
--- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs
+++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs
@@ -2,8 +2,8 @@
 
 use juniper::{
     execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation,
-    EmptySubscription, Executor, FieldError, FieldResult, GraphQLObject, GraphQLType,
-    IntoFieldError, RootNode, ScalarValue, Variables,
+    EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject,
+    GraphQLType, IntoFieldError, RootNode, ScalarValue, Variables,
 };
 
 fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
@@ -46,13 +46,13 @@ mod no_implers {
 
     struct QueryRoot;
 
-    #[graphql_object]
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
     impl QueryRoot {
         fn character(&self) -> CharacterValue {
             unimplemented!()
         }
 
-        fn hero(&self) -> Box<DynHero<'_, __S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             unimplemented!()
         }
     }
@@ -183,8 +183,8 @@ mod trivial {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -200,7 +200,7 @@ mod trivial {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -705,8 +705,8 @@ mod trivial_async {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -722,7 +722,7 @@ mod trivial_async {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -1068,8 +1068,8 @@ mod explicit_async {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -1085,7 +1085,7 @@ mod explicit_async {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -1318,8 +1318,8 @@ mod fallible_field {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -1335,7 +1335,7 @@ mod fallible_field {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -1584,8 +1584,8 @@ mod generic {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -1601,7 +1601,7 @@ mod generic {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, u8, (), S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, u8, (), S>> {
             let ch: Box<DynHero<'_, u8, (), _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -1725,6 +1725,7 @@ mod generic {
             );
         }
     }
+
     #[tokio::test]
     async fn dyn_resolves_info_field() {
         const DOC: &str = r#"{
@@ -1828,8 +1829,8 @@ mod generic_async {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -1845,7 +1846,7 @@ mod generic_async {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, u8, (), S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, u8, (), S>> {
             let ch: Box<DynHero<'_, u8, (), _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -1969,6 +1970,7 @@ mod generic_async {
             );
         }
     }
+
     #[tokio::test]
     async fn dyn_resolves_info_field() {
         const DOC: &str = r#"{
@@ -2072,8 +2074,8 @@ mod generic_lifetime_async {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue<'_, ()> {
             match self {
                 Self::Human => Human {
@@ -2089,7 +2091,7 @@ mod generic_lifetime_async {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, '_, (), S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, '_, (), S>> {
             let ch: Box<DynHero<'_, '_, (), _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -2213,6 +2215,7 @@ mod generic_lifetime_async {
             );
         }
     }
+
     #[tokio::test]
     async fn dyn_resolves_info_field() {
         const DOC: &str = r#"{
@@ -2261,11 +2264,15 @@ mod argument {
     #[graphql_interface(for = Human)]
     trait Character {
         fn id_wide(&self, is_number: bool) -> &str;
+
+        async fn id_wide2(&self, is_number: bool, r#async: Option<i32>) -> &str;
     }
 
     #[graphql_interface(dyn = DynHero, for = Human)]
     trait Hero {
         fn info_wide(&self, is_planet: bool) -> &str;
+
+        async fn info_wide2(&self, is_planet: bool, r#async: Option<i32>) -> &str;
     }
 
     #[derive(GraphQLObject)]
@@ -2284,6 +2291,14 @@ mod argument {
                 "none"
             }
         }
+
+        async fn id_wide2(&self, is_number: bool, _: Option<i32>) -> &str {
+            if is_number {
+                &self.id
+            } else {
+                "none"
+            }
+        }
     }
 
     #[graphql_interface(dyn)]
@@ -2295,12 +2310,20 @@ mod argument {
                 &self.id
             }
         }
+
+        async fn info_wide2(&self, is_planet: bool, _: Option<i32>) -> &str {
+            if is_planet {
+                &self.home_planet
+            } else {
+                &self.id
+            }
+        }
     }
 
     struct QueryRoot;
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             Human {
                 id: "human-32".to_string(),
@@ -2309,7 +2332,7 @@ mod argument {
             .into()
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             Box::new(Human {
                 id: "human-32".to_string(),
                 home_planet: "earth".to_string(),
@@ -2322,14 +2345,26 @@ mod argument {
         let schema = schema(QueryRoot);
 
         for (input, expected) in &[
-            ("{ character { idWide(isNumber: true) } }", "human-32"),
-            ("{ character { idWide(isNumber: false) } }", "none"),
+            (
+                "{ character { idWide(isNumber: true), idWide2(isNumber: true) } }",
+                "human-32",
+            ),
+            (
+                "{ character { idWide(isNumber: false), idWide2(isNumber: false, async: 5) } }",
+                "none",
+            ),
         ] {
             let expected: &str = *expected;
 
             assert_eq!(
                 execute(*input, None, &schema, &Variables::new(), &()).await,
-                Ok((graphql_value!({"character": {"idWide": expected}}), vec![])),
+                Ok((
+                    graphql_value!({"character": {
+                        "idWide": expected,
+                        "idWide2": expected,
+                    }}),
+                    vec![],
+                )),
             );
         }
     }
@@ -2339,14 +2374,26 @@ mod argument {
         let schema = schema(QueryRoot);
 
         for (input, expected) in &[
-            ("{ hero { infoWide(isPlanet: true) } }", "earth"),
-            ("{ hero { infoWide(isPlanet: false) } }", "human-32"),
+            (
+                "{ hero { infoWide(isPlanet: true), infoWide2(isPlanet: true) } }",
+                "earth",
+            ),
+            (
+                "{ hero { infoWide(isPlanet: false), infoWide2(isPlanet: false, async: 3) } }",
+                "human-32",
+            ),
         ] {
             let expected: &str = *expected;
 
             assert_eq!(
                 execute(*input, None, &schema, &Variables::new(), &()).await,
-                Ok((graphql_value!({"hero": {"infoWide": expected}}), vec![])),
+                Ok((
+                    graphql_value!({"hero": {
+                        "infoWide": expected,
+                        "infoWide2": expected,
+                    }}),
+                    vec![],
+                )),
             );
         }
     }
@@ -2374,13 +2421,23 @@ mod argument {
             );
 
             let expected_field_name: &str = *field;
+            let expected_field_name2: &str = &format!("{}2", field);
             let expected_arg_name: &str = *arg;
             assert_eq!(
                 execute(&doc, None, &schema, &Variables::new(), &()).await,
                 Ok((
-                    graphql_value!({"__type": {"fields": [
-                        {"name": expected_field_name, "args": [{"name": expected_arg_name}]},
-                    ]}}),
+                    graphql_value!({"__type": {"fields": [{
+                        "name": expected_field_name,
+                        "args": [
+                            {"name": expected_arg_name},
+                        ],
+                    }, {
+                        "name": expected_field_name2,
+                        "args": [
+                            {"name": expected_arg_name},
+                            {"name": "async"},
+                        ],
+                    }]}}),
                     vec![],
                 )),
             );
@@ -2408,7 +2465,10 @@ mod argument {
             assert_eq!(
                 execute(&doc, None, &schema, &Variables::new(), &()).await,
                 Ok((
-                    graphql_value!({"__type": { "fields": [{"args": [{"description": None}]}]}}),
+                    graphql_value!({"__type": {"fields": [
+                        {"args": [{"description": None}]},
+                        {"args": [{"description": None}, {"description": None}]},
+                    ]}}),
                     vec![],
                 )),
             );
@@ -2436,7 +2496,10 @@ mod argument {
             assert_eq!(
                 execute(&doc, None, &schema, &Variables::new(), &()).await,
                 Ok((
-                    graphql_value!({"__type": { "fields": [{"args": [{"defaultValue": None}]}]}}),
+                    graphql_value!({"__type": {"fields": [
+                        {"args": [{"defaultValue": None}]},
+                        {"args": [{"defaultValue": None}, {"defaultValue": None}]},
+                    ]}}),
                     vec![],
                 )),
             );
@@ -2447,6 +2510,11 @@ mod argument {
 mod default_argument {
     use super::*;
 
+    #[derive(GraphQLInputObject, Debug)]
+    struct Point {
+        x: i32,
+    }
+
     #[graphql_interface(for = Human)]
     trait Character {
         async fn id(
@@ -2455,12 +2523,17 @@ mod default_argument {
             #[graphql(default = "second".to_string())] second: String,
             #[graphql(default = "t")] third: String,
         ) -> String;
+
+        fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 {
+            coord.x
+        }
     }
 
     #[derive(GraphQLObject)]
     #[graphql(impl = CharacterValue)]
     struct Human {
         id: String,
+        info: i32,
     }
 
     #[graphql_interface]
@@ -2477,6 +2550,7 @@ mod default_argument {
         fn character(&self) -> CharacterValue {
             Human {
                 id: "human-32".to_string(),
+                info: 0,
             }
             .into()
         }
@@ -2508,6 +2582,23 @@ mod default_argument {
         }
     }
 
+    #[tokio::test]
+    async fn resolves_info_field() {
+        let schema = schema(QueryRoot);
+
+        for (input, expected) in &[
+            ("{ character { info } }", 1),
+            ("{ character { info(coord: {x: 2}) } }", 2),
+        ] {
+            let expected: i32 = *expected;
+
+            assert_eq!(
+                execute(*input, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"character": {"info": expected}}), vec![])),
+            );
+        }
+    }
+
     #[tokio::test]
     async fn has_defaults() {
         const DOC: &str = r#"{
@@ -2516,6 +2607,12 @@ mod default_argument {
                     args {
                         name
                         defaultValue
+                        type {
+                            name
+                            ofType {
+                                name
+                            }
+                        }
                     }
                 }
             }
@@ -2526,11 +2623,27 @@ mod default_argument {
         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""#},
-                ]}]}}),
+                graphql_value!({"__type": {"fields": [{
+                    "args": [{
+                        "name": "first",
+                        "defaultValue": r#""""#,
+                        "type": {"name": "String", "ofType": None},
+                    }, {
+                        "name": "second",
+                        "defaultValue": r#""second""#,
+                        "type": {"name": "String", "ofType": None},
+                    }, {
+                        "name": "third",
+                        "defaultValue": r#""t""#,
+                        "type": {"name": "String", "ofType": None},
+                    }],
+                }, {
+                    "args": [{
+                        "name": "coord",
+                        "defaultValue": "{x: 1}",
+                        "type": {"name": "Point", "ofType": None},
+                    }],
+                }]}}),
                 vec![],
             )),
         );
@@ -2544,6 +2657,7 @@ mod description_from_doc_comment {
     #[graphql_interface(for = Human)]
     trait Character {
         /// Rust `id` docs.
+        /// Long.
         fn id(&self) -> &str;
     }
 
@@ -2591,7 +2705,8 @@ mod description_from_doc_comment {
             execute(DOC, None, &schema, &Variables::new(), &()).await,
             Ok((
                 graphql_value!({"__type": {
-                    "description": "Rust docs.", "fields": [{"description": "Rust `id` docs."}],
+                    "description": "Rust docs.",
+                    "fields": [{"description": "Rust `id` docs.\nLong."}],
                 }}),
                 vec![],
             )),
@@ -2600,8 +2715,6 @@ mod description_from_doc_comment {
 }
 
 mod deprecation_from_attr {
-    #![allow(deprecated)]
-
     use super::*;
 
     #[graphql_interface(for = Human)]
@@ -2675,7 +2788,7 @@ mod deprecation_from_attr {
 
         assert_eq!(
             execute(DOC, None, &schema, &Variables::new(), &()).await,
-            Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![],)),
+            Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![])),
         );
     }
 
@@ -2733,8 +2846,6 @@ mod deprecation_from_attr {
 }
 
 mod explicit_name_description_and_deprecation {
-    #![allow(deprecated)]
-
     use super::*;
 
     /// Rust docs.
@@ -2743,10 +2854,7 @@ mod explicit_name_description_and_deprecation {
         /// Rust `id` docs.
         #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")]
         #[deprecated(note = "Should be omitted.")]
-        fn id(
-            &self,
-            #[graphql(name = "myName", desc = "My argument.", default)] n: Option<String>,
-        ) -> &str;
+        fn id(&self, #[graphql(name = "myName", desc = "My argument.")] n: Option<String>) -> &str;
 
         #[graphql(deprecated)]
         #[deprecated(note = "Should be omitted.")]
@@ -2918,6 +3026,107 @@ mod explicit_name_description_and_deprecation {
     }
 }
 
+mod renamed_all_fields_and_args {
+    use super::*;
+
+    #[graphql_interface(rename_all = "none", for = Human)]
+    trait Character {
+        fn id(&self) -> &str {
+            "human-32"
+        }
+
+        async fn home_planet(&self, planet_name: String) -> String {
+            planet_name
+        }
+
+        async fn r#async_info(&self, r#my_num: i32) -> i32 {
+            r#my_num
+        }
+    }
+
+    struct Human;
+
+    #[graphql_object(rename_all = "none", impl = CharacterValue)]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        fn home_planet(planet_name: String) -> String {
+            planet_name
+        }
+
+        fn r#async_info(r#my_num: i32) -> i32 {
+            r#my_num
+        }
+    }
+
+    #[graphql_interface]
+    impl Character for Human {}
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            Human.into()
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            character {
+                id
+                home_planet(planet_name: "earth")
+                async_info(my_num: 3)
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {
+                    "id": "human-32",
+                    "home_planet": "earth",
+                    "async_info": 3,
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_correct_fields_and_args_names() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": []},
+                    {"name": "home_planet", "args": [{"name": "planet_name"}]},
+                    {"name": "async_info", "args": [{"name": "my_num"}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
 mod explicit_scalar {
     use super::*;
 
@@ -3430,9 +3639,9 @@ mod explicit_generic_scalar {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
-        fn character(&self) -> CharacterValue<S> {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
+        fn character<S: ScalarValue>(&self) -> CharacterValue<S> {
             match self {
                 Self::Human => Human {
                     id: "human-32".to_string(),
@@ -3447,7 +3656,230 @@ mod explicit_generic_scalar {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
+            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 bounded_generic_scalar {
+    use super::*;
+
+    #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)]
+    trait Character {
+        fn id(&self) -> &str;
+    }
+
+    #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S: ScalarValue + Clone)]
+    trait Hero {
+        async fn info(&self) -> &str;
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue, DynHero<S>], scalar = S: ScalarValue + Clone)]
+    struct Human {
+        id: String,
+        home_planet: String,
+    }
+
+    #[graphql_interface(scalar = S: ScalarValue + Clone)]
+    impl Character for Human {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)]
+    impl Hero for Human {
+        async fn info(&self) -> &str {
+            &self.home_planet
+        }
+    }
+
+    #[derive(GraphQLObject)]
+    #[graphql(impl = [CharacterValue, DynHero<__S>])]
+    struct Droid {
+        id: String,
+        primary_function: String,
+    }
+
+    #[graphql_interface(scalar = S: ScalarValue + Clone)]
+    impl Character for Droid {
+        fn id(&self) -> &str {
+            &self.id
+        }
+    }
+
+    #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)]
+    impl Hero for Droid {
+        async fn info(&self) -> &str {
+            &self.primary_function
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object(scalar = S: ScalarValue + Clone + Send + Sync)]
+    impl QueryRoot {
+        fn character(&self) -> CharacterValue {
+            match self {
+                Self::Human => Human {
+                    id: "human-32".to_string(),
+                    home_planet: "earth".to_string(),
+                }
+                .into(),
+                Self::Droid => Droid {
+                    id: "droid-99".to_string(),
+                    primary_function: "run".to_string(),
+                }
+                .into(),
+            }
+        }
+
+        fn hero<S: ScalarValue + Clone + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -3595,7 +4027,7 @@ mod explicit_generic_scalar {
 mod explicit_custom_context {
     use super::*;
 
-    pub struct CustomContext;
+    struct CustomContext;
 
     impl juniper::Context for CustomContext {}
 
@@ -3698,8 +4130,8 @@ mod explicit_custom_context {
         Droid,
     }
 
-    #[graphql_object(context = CustomContext, scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -3715,7 +4147,7 @@ mod explicit_custom_context {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -3862,7 +4294,7 @@ mod explicit_custom_context {
 mod inferred_custom_context_from_field {
     use super::*;
 
-    pub struct CustomContext(String);
+    struct CustomContext(String);
 
     impl juniper::Context for CustomContext {}
 
@@ -3944,8 +4376,8 @@ mod inferred_custom_context_from_field {
         Droid,
     }
 
-    #[graphql_object(context = CustomContext, scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -3961,7 +4393,7 @@ mod inferred_custom_context_from_field {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -4196,8 +4628,8 @@ mod inferred_custom_context_from_downcast {
         Droid,
     }
 
-    #[graphql_object(context = Database, scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -4213,7 +4645,7 @@ mod inferred_custom_context_from_downcast {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -4390,6 +4822,7 @@ mod executor {
 
         async fn info<'b>(
             &'b self,
+            arg: Option<i32>,
             #[graphql(executor)] another: &Executor<'_, '_, (), S>,
         ) -> &'b str
         where
@@ -4407,6 +4840,7 @@ mod executor {
 
         async fn info<'b>(
             &'b self,
+            arg: Option<i32>,
             #[graphql(executor)] another: &Executor<'_, '_, (), S>,
         ) -> &'b str
         where
@@ -4422,7 +4856,7 @@ mod executor {
 
     #[graphql_interface(scalar = S)]
     impl<S: ScalarValue> Character<S> for Human {
-        async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+        async fn info<'b>(&'b self, _: Option<i32>, _: &Executor<'_, '_, (), S>) -> &'b str
         where
             S: Send + Sync,
         {
@@ -4432,7 +4866,7 @@ mod executor {
 
     #[graphql_interface(dyn, scalar = S)]
     impl<S: ScalarValue> Hero<S> for Human {
-        async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+        async fn info<'b>(&'b self, _: Option<i32>, _: &Executor<'_, '_, (), S>) -> &'b str
         where
             S: Send + Sync,
         {
@@ -4449,7 +4883,7 @@ mod executor {
 
     #[graphql_interface(scalar = S)]
     impl<S: ScalarValue> Character<S> for Droid {
-        async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+        async fn info<'b>(&'b self, _: Option<i32>, _: &Executor<'_, '_, (), S>) -> &'b str
         where
             S: Send + Sync,
         {
@@ -4459,7 +4893,7 @@ mod executor {
 
     #[graphql_interface(dyn, scalar = S)]
     impl<S: ScalarValue> Hero<S> for Droid {
-        async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
+        async fn info<'b>(&'b self, _: Option<i32>, _: &Executor<'_, '_, (), S>) -> &'b str
         where
             S: Send + Sync,
         {
@@ -4622,6 +5056,38 @@ mod executor {
             }
         }
     }
+
+    #[tokio::test]
+    async fn not_arg() {
+        for interface in &["Character", "Hero"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        fields {{
+                            name
+                            args {{
+                                name
+                            }}
+                        }}
+                    }}
+                }}"#,
+                interface,
+            );
+
+            let schema = schema(QueryRoot::Human);
+
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((
+                    graphql_value!({"__type": {"fields": [
+                        {"name": "id", "args": []},
+                        {"name": "info", "args": [{"name": "arg"}]},
+                    ]}}),
+                    vec![],
+                )),
+            );
+        }
+    }
 }
 
 mod ignored_method {
@@ -4806,8 +5272,8 @@ mod downcast_method {
         Droid,
     }
 
-    #[graphql_object(scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -4823,7 +5289,7 @@ mod downcast_method {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
@@ -5071,8 +5537,8 @@ mod external_downcast {
         Droid,
     }
 
-    #[graphql_object(context = Database, scalar = S)]
-    impl<S: ScalarValue + Send + Sync> QueryRoot {
+    #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)]
+    impl QueryRoot {
         fn character(&self) -> CharacterValue {
             match self {
                 Self::Human => Human {
@@ -5088,7 +5554,7 @@ mod external_downcast {
             }
         }
 
-        fn hero(&self) -> Box<DynHero<'_, S>> {
+        fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> {
             let ch: Box<DynHero<'_, _>> = match self {
                 Self::Human => Box::new(Human {
                     id: "human-32".to_string(),
diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs
index 0b45de8d3..6348a66c8 100644
--- a/integration_tests/juniper_tests/src/codegen/mod.rs
+++ b/integration_tests/juniper_tests/src/codegen/mod.rs
@@ -1,11 +1,12 @@
 mod derive_enum;
 mod derive_input_object;
-mod derive_object;
 mod derive_object_with_raw_idents;
 mod derive_scalar;
-mod impl_object;
 mod impl_scalar;
 mod interface_attr;
+mod object_attr;
+mod object_derive;
 mod scalar_value_transparent;
+mod subscription_attr;
 mod union_attr;
 mod union_derive;
diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs
new file mode 100644
index 000000000..797a87fc8
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs
@@ -0,0 +1,2124 @@
+//! Tests for `#[graphql_object]` macro.
+
+use juniper::{
+    execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription,
+    Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLType,
+    IntoFieldError, RootNode, ScalarValue, Variables,
+};
+
+fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
+where
+    Q: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
+{
+    RootNode::new(
+        query_root,
+        EmptyMutation::<C>::new(),
+        EmptySubscription::<C>::new(),
+    )
+}
+
+fn schema_with_scalar<'q, S, C, 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_with_scalar_value(
+        query_root,
+        EmptyMutation::<C>::new(),
+        EmptySubscription::<C>::new(),
+    )
+}
+
+mod trivial {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_object() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+        );
+    }
+}
+
+mod trivial_async {
+    use futures::future;
+
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        async fn id() -> &'static str {
+            future::ready("human-32").await
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_object() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+        );
+    }
+}
+
+mod raw_method {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        fn r#my_id() -> &'static str {
+            "human-32"
+        }
+
+        async fn r#async() -> &'static str {
+            "async-32"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                myId
+                async
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"myId": "human-32", "async": "async-32"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+                kind
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "Human",
+                    "kind": "OBJECT",
+                    "fields": [{"name": "myId"}, {"name": "async"}],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod ignored_method {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        #[allow(dead_code)]
+        #[graphql(ignore)]
+        fn planet() -> &'static str {
+            "earth"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_not_field() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{"name": "id"}]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod fallible_method {
+    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"}))
+        }
+    }
+
+    struct Human {
+        id: String,
+    }
+
+    #[graphql_object]
+    impl Human {
+        fn id(&self) -> Result<&str, CustomError> {
+            Ok(&self.id)
+        }
+
+        async fn home_planet<__S>() -> FieldResult<&'static str, __S> {
+            Ok("earth")
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human {
+                id: "human-32".to_string(),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+                homePlanet
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_graphql_type() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+                kind
+                fields {
+                    name
+                    type {
+                        kind
+                        ofType {
+                            name
+                        }
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "Human",
+                    "kind": "OBJECT",
+                    "fields": [{
+                        "name": "id",
+                        "type": {"kind": "NON_NULL", "ofType": {"name": "String"}},
+                    }, {
+                        "name": "homePlanet",
+                        "type": {"kind": "NON_NULL", "ofType": {"name": "String"}},
+                    }]
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod generic {
+    use super::*;
+
+    struct Human<A = (), B: ?Sized = ()> {
+        id: A,
+        _home_planet: B,
+    }
+
+    #[graphql_object]
+    impl<B: ?Sized> Human<i32, B> {
+        fn id(&self) -> i32 {
+            self.id
+        }
+    }
+
+    #[graphql_object(name = "HumanString")]
+    impl<B: ?Sized> Human<String, B> {
+        fn id(&self) -> &str {
+            self.id.as_str()
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human(&self) -> Human<i32> {
+            Human {
+                id: 32,
+                _home_planet: (),
+            }
+        }
+
+        fn human_string(&self) -> Human<String> {
+            Human {
+                id: "human-32".into(),
+                _home_planet: (),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": 32}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string() {
+        const DOC: &str = r#"{
+            humanString {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"humanString": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod generic_async {
+    use super::*;
+
+    struct Human<A = (), B: ?Sized = ()> {
+        id: A,
+        _home_planet: B,
+    }
+
+    #[graphql_object]
+    impl<B: ?Sized> Human<i32, B> {
+        async fn id(&self) -> i32 {
+            self.id
+        }
+    }
+
+    #[graphql_object(name = "HumanString")]
+    impl<B: ?Sized> Human<String, B> {
+        async fn id(&self) -> &str {
+            self.id.as_str()
+        }
+    }
+
+    #[derive(Clone, Copy)]
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human(&self) -> Human<i32> {
+            Human {
+                id: 32,
+                _home_planet: (),
+            }
+        }
+
+        fn human_string(&self) -> Human<String> {
+            Human {
+                id: "human-32".into(),
+                _home_planet: (),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": 32}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string() {
+        const DOC: &str = r#"{
+            humanString {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"humanString": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod generic_lifetime_async {
+    use super::*;
+
+    struct Human<'p, A = ()> {
+        id: A,
+        home_planet: &'p str,
+    }
+
+    #[graphql_object]
+    impl<'p> Human<'p, i32> {
+        async fn id(&self) -> i32 {
+            self.id
+        }
+
+        async fn planet(&self) -> &str {
+            self.home_planet
+        }
+    }
+
+    #[graphql_object(name = "HumanString")]
+    impl<'id, 'p> Human<'p, &'id str> {
+        async fn id(&self) -> &str {
+            self.id
+        }
+
+        async fn planet(&self) -> &str {
+            self.home_planet
+        }
+    }
+
+    #[derive(Clone)]
+    struct QueryRoot(String);
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human(&self) -> Human<'static, i32> {
+            Human {
+                id: 32,
+                home_planet: "earth",
+            }
+        }
+
+        fn human_string(&self) -> Human<'_, &str> {
+            Human {
+                id: self.0.as_str(),
+                home_planet: self.0.as_str(),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human() {
+        const DOC: &str = r#"{
+            human {
+                id
+                planet
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"id": 32, "planet": "earth"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string() {
+        const DOC: &str = r#"{
+            humanString {
+                id
+                planet
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"humanString": {"id": "mars", "planet": "mars"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod nested_generic_lifetime_async {
+    use super::*;
+
+    struct Droid<'p, A = ()> {
+        id: A,
+        primary_function: &'p str,
+    }
+
+    #[graphql_object]
+    impl<'p> Droid<'p, i32> {
+        async fn id(&self) -> i32 {
+            self.id
+        }
+
+        async fn primary_function(&self) -> &str {
+            self.primary_function
+        }
+    }
+
+    #[graphql_object(name = "DroidString")]
+    impl<'id, 'p> Droid<'p, &'id str> {
+        async fn id(&self) -> &str {
+            self.id
+        }
+
+        async fn primary_function(&self) -> &str {
+            self.primary_function
+        }
+    }
+
+    struct Human<'p, A = ()> {
+        id: A,
+        home_planet: &'p str,
+    }
+
+    #[graphql_object]
+    impl<'p> Human<'p, i32> {
+        async fn id(&self) -> i32 {
+            self.id
+        }
+
+        async fn planet(&self) -> &str {
+            self.home_planet
+        }
+
+        async fn droid(&self) -> Droid<'_, i32> {
+            Droid {
+                id: self.id,
+                primary_function: "run",
+            }
+        }
+    }
+
+    #[graphql_object(name = "HumanString")]
+    impl<'id, 'p> Human<'p, &'id str> {
+        async fn id(&self) -> &str {
+            self.id
+        }
+
+        async fn planet(&self) -> &str {
+            self.home_planet
+        }
+
+        async fn droid(&self) -> Droid<'_, &str> {
+            Droid {
+                id: "none",
+                primary_function: self.home_planet,
+            }
+        }
+    }
+
+    #[derive(Clone)]
+    struct QueryRoot(String);
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human(&self) -> Human<'static, i32> {
+            Human {
+                id: 32,
+                home_planet: "earth",
+            }
+        }
+
+        fn human_string(&self) -> Human<'_, &str> {
+            Human {
+                id: self.0.as_str(),
+                home_planet: self.0.as_str(),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human() {
+        const DOC: &str = r#"{
+            human {
+                id
+                planet
+                droid {
+                    id
+                    primaryFunction
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": 32,
+                    "planet": "earth",
+                    "droid": {
+                        "id": 32,
+                        "primaryFunction": "run",
+                    },
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string() {
+        const DOC: &str = r#"{
+            humanString {
+                id
+                planet
+                droid {
+                    id
+                    primaryFunction
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"humanString": {
+                    "id": "mars",
+                    "planet": "mars",
+                    "droid": {
+                        "id": "none",
+                        "primaryFunction": "mars",
+                    },
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        for object in &["Human", "Droid"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        name
+                    }}
+                }}"#,
+                object,
+            );
+
+            let schema = schema(QueryRoot("mars".into()));
+
+            let expected_name: &str = *object;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])),
+            );
+        }
+    }
+}
+
+mod argument {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        fn id(arg: String) -> String {
+            arg
+        }
+
+        async fn home_planet(&self, r#raw_arg: String, r#async: Option<i32>) -> String {
+            format!("{},{:?}", r#raw_arg, r#async)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id(arg: "human-32")
+                homePlanet(rawArg: "earth")
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"id": "human-32", "homePlanet": "earth,None"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": [{"name": "arg"}]},
+                    {"name": "homePlanet", "args": [{"name": "rawArg"}, {"name": "async"}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    args {
+                        description
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"args": [{"description": None}]},
+                    {"args": [{"description": None}, {"description": None}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_defaults() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    args {
+                        defaultValue
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"args": [{"defaultValue": None}]},
+                    {"args": [{"defaultValue": None}, {"defaultValue": None}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod default_argument {
+    use super::*;
+
+    #[derive(GraphQLInputObject, Debug)]
+    struct Point {
+        x: i32,
+    }
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        fn id(
+            #[graphql(default)] arg1: i32,
+            #[graphql(default = "second".to_string())] arg2: String,
+            #[graphql(default = true)] r#arg3: bool,
+        ) -> String {
+            format!("{}|{}&{}", arg1, arg2, r#arg3)
+        }
+
+        fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> i32 {
+            coord.x
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        let schema = schema(QueryRoot);
+
+        for (input, expected) in &[
+            ("{ human { id } }", "0|second&true"),
+            ("{ human { id(arg1: 1) } }", "1|second&true"),
+            (r#"{ human { id(arg2: "") } }"#, "0|&true"),
+            (r#"{ human { id(arg1: 2, arg2: "") } }"#, "2|&true"),
+            (
+                r#"{ human { id(arg1: 1, arg2: "", arg3: false) } }"#,
+                "1|&false",
+            ),
+        ] {
+            let expected: &str = *expected;
+
+            assert_eq!(
+                execute(*input, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"human": {"id": expected}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_info_field() {
+        let schema = schema(QueryRoot);
+
+        for (input, expected) in &[
+            ("{ human { info } }", 1),
+            ("{ human { info(coord: { x: 2 }) } }", 2),
+        ] {
+            let expected: i32 = *expected;
+
+            assert_eq!(
+                execute(*input, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"human": {"info": expected}}), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn has_defaults() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    args {
+                        name
+                        defaultValue
+                        type {
+                            name
+                            ofType {
+                                name
+                            }
+                        }
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{
+                    "args": [{
+                        "name": "arg1",
+                        "defaultValue": "0",
+                        "type": {"name": "Int", "ofType": None},
+                    }, {
+                        "name": "arg2",
+                        "defaultValue": r#""second""#,
+                        "type": {"name": "String", "ofType": None},
+                    }, {
+                        "name": "arg3",
+                        "defaultValue": "true",
+                        "type": {"name": "Boolean", "ofType": None},
+                    }],
+                }, {
+                    "args": [{
+                        "name": "coord",
+                        "defaultValue": "{x: 1}",
+                        "type": {"name": "Point", "ofType": None},
+                    }],
+                }]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    struct Human;
+
+    /// Rust docs.
+    #[graphql_object]
+    impl Human {
+        /// Rust `id` docs.
+        fn id() -> &'static str {
+            "human-32"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_doc_comment_as_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                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 {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        #[deprecated]
+        fn a(&self) -> &str {
+            "a"
+        }
+
+        #[deprecated(note = "Use `id`.")]
+        fn b(&self) -> &str {
+            "b"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_fields() {
+        const DOC: &str = r#"{
+            human {
+                a
+                b
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"a": "a", "b": "b"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn deprecates_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                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: "Human") {
+                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 {
+    use super::*;
+
+    struct Human;
+
+    /// Rust docs.
+    #[graphql_object(name = "MyHuman", desc = "My human.")]
+    impl Human {
+        /// Rust `id` docs.
+        #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")]
+        #[deprecated(note = "Should be omitted.")]
+        fn id(
+            #[graphql(name = "myName", desc = "My argument.", default)] _n: String,
+        ) -> &'static str {
+            "human-32"
+        }
+
+        #[graphql(deprecated)]
+        #[deprecated(note = "Should be omitted.")]
+        fn a(&self) -> &str {
+            "a"
+        }
+
+        fn b(&self) -> &str {
+            "b"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                myId
+                a
+                b
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"myId": "human-32", "a": "a", "b": "b"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_name() {
+        const DOC: &str = r#"{
+            __type(name: "MyHuman") {
+                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": "MyHuman",
+                    "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: "MyHuman") {
+                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 human.",
+                    "fields": [{
+                        "name": "myId",
+                        "description": "My human 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: "MyHuman") {
+                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 renamed_all_fields_and_args {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object(rename_all = "none")]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        async fn home_planet(&self, planet_name: String) -> String {
+            planet_name
+        }
+
+        async fn r#async_info(r#my_num: i32) -> i32 {
+            r#my_num
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                home_planet(planet_name: "earth")
+                async_info(my_num: 3)
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "human-32",
+                    "home_planet": "earth",
+                    "async_info": 3,
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_correct_fields_and_args_names() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": []},
+                    {"name": "home_planet", "args": [{"name": "planet_name"}]},
+                    {"name": "async_info", "args": [{"name": "my_num"}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_scalar {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object(scalar = DefaultScalarValue)]
+    impl Human {
+        fn id(&self) -> &str {
+            "human-32"
+        }
+
+        async fn home_planet() -> &'static str {
+            "earth"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = DefaultScalarValue)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                homePlanet
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod custom_scalar {
+    use crate::custom_scalar::MyScalarValue;
+
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object(scalar = MyScalarValue)]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        async fn home_planet(&self) -> &str {
+            "earth"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = MyScalarValue)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                homePlanet
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_generic_scalar {
+    use std::marker::PhantomData;
+
+    use super::*;
+
+    struct Human<S>(PhantomData<S>);
+
+    #[graphql_object(scalar = S)]
+    impl<S: ScalarValue> Human<S> {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        async fn another(&self, _executor: &Executor<'_, '_, (), S>) -> Human<S> {
+            Human(PhantomData)
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human<__S>() -> Human<__S> {
+            Human(PhantomData)
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                another {
+                    id
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "human-32",
+                    "another": {"id": "human-32"},
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object(scalar = S: ScalarValue + Clone)]
+    impl Human {
+        fn id() -> &'static str {
+            "human-32"
+        }
+
+        async fn another<S>(&self, _executor: &Executor<'_, '_, (), S>) -> Human {
+            Human
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                another {
+                    id
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "human-32",
+                    "another": {"id": "human-32"},
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_custom_context {
+    use super::*;
+
+    struct CustomContext(String);
+
+    impl juniper::Context for CustomContext {}
+
+    struct Human;
+
+    #[graphql_object(context = CustomContext)]
+    impl Human {
+        async fn id<'c>(&self, context: &'c CustomContext) -> &'c str {
+            context.0.as_str()
+        }
+
+        async fn info(_ctx: &()) -> &'static str {
+            "human being"
+        }
+
+        fn more(#[graphql(context)] custom: &CustomContext) -> &str {
+            custom.0.as_str()
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(context = CustomContext)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                info
+                more
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "ctx!",
+                    "info": "human being",
+                    "more": "ctx!",
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod inferred_custom_context_from_field {
+    use super::*;
+
+    struct CustomContext(String);
+
+    impl juniper::Context for CustomContext {}
+
+    struct Human;
+
+    #[graphql_object]
+    impl Human {
+        async fn id<'c>(&self, context: &'c CustomContext) -> &'c str {
+            context.0.as_str()
+        }
+
+        async fn info(_ctx: &()) -> &'static str {
+            "human being"
+        }
+
+        fn more(#[graphql(context)] custom: &CustomContext) -> &str {
+            custom.0.as_str()
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(context = CustomContext)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                info
+                more
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "ctx!",
+                    "info": "human being",
+                    "more": "ctx!",
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod executor {
+    use juniper::LookAheadMethods as _;
+
+    use super::*;
+
+    struct Human;
+
+    #[graphql_object(scalar = S: ScalarValue)]
+    impl Human {
+        async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> &'e str
+        where
+            S: ScalarValue,
+        {
+            executor.look_ahead().field_name()
+        }
+
+        fn info<S>(
+            &self,
+            arg: String,
+            #[graphql(executor)] _another: &Executor<'_, '_, (), S>,
+        ) -> String {
+            arg
+        }
+
+        fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> &'e str {
+            "no info"
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                info(arg: "input!")
+                info2
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "id",
+                    "info": "input!",
+                    "info2": "no info",
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn not_arg() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": []},
+                    {"name": "info", "args": [{"name": "arg"}]},
+                    {"name": "info2", "args": []},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod switched_context {
+    use super::*;
+
+    struct CustomContext;
+
+    impl juniper::Context for CustomContext {}
+
+    #[derive(GraphQLObject)]
+    #[graphql(context = CustomContext)]
+    struct Droid {
+        id: i32,
+    }
+
+    struct Human;
+
+    #[graphql_object(context = CustomContext)]
+    impl Human {
+        fn switch_always<'e, S: ScalarValue>(
+            executor: &'e Executor<'_, '_, CustomContext, S>,
+        ) -> (&'e CustomContext, Droid) {
+            (executor.context(), Droid { id: 0 })
+        }
+
+        async fn switch_opt<'e, S: ScalarValue>(
+            executor: &'e Executor<'_, '_, CustomContext, S>,
+        ) -> Option<(&'e CustomContext, Droid)> {
+            Some((executor.context(), Droid { id: 1 }))
+        }
+
+        fn switch_res<'e, S: ScalarValue>(
+            &self,
+            executor: &'e Executor<'_, '_, CustomContext, S>,
+        ) -> FieldResult<(&'e CustomContext, Droid)> {
+            Ok((executor.context(), Droid { id: 2 }))
+        }
+
+        async fn switch_res_opt<'e, S: ScalarValue>(
+            &self,
+            executor: &'e Executor<'_, '_, CustomContext, S>,
+        ) -> FieldResult<Option<(&'e CustomContext, Droid)>> {
+            Ok(Some((executor.context(), Droid { id: 3 })))
+        }
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(context = CustomContext)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                switchAlways { id }
+                switchOpt { id }
+                switchRes { id }
+                switchResOpt { id }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+        let ctx = CustomContext;
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).await,
+            Ok((
+                graphql_value!({"human": {
+                    "switchAlways": {"id": 0},
+                    "switchOpt": {"id": 1},
+                    "switchRes": {"id": 2},
+                    "switchResOpt": {"id": 3},
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_correct_fields_types() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    type {
+                        kind
+                        name
+                        ofType {
+                            name
+                        }
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+        let ctx = CustomContext;
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{
+                    "name": "switchAlways",
+                    "type": {
+                        "kind": "NON_NULL",
+                        "name": None,
+                        "ofType": {"name": "Droid"},
+                    },
+                }, {
+                    "name": "switchOpt",
+                    "type": {
+                        "kind": "OBJECT",
+                        "name": "Droid",
+                        "ofType": None,
+                    },
+                }, {
+                    "name": "switchRes",
+                    "type": {
+                        "kind": "NON_NULL",
+                        "name": None,
+                        "ofType": {"name": "Droid"},
+                    },
+                }, {
+                    "name": "switchResOpt",
+                    "type": {
+                        "kind": "OBJECT",
+                        "name": "Droid",
+                        "ofType": None,
+                    },
+                }]}}),
+                vec![],
+            )),
+        );
+    }
+}
diff --git a/integration_tests/juniper_tests/src/codegen/object_derive.rs b/integration_tests/juniper_tests/src/codegen/object_derive.rs
new file mode 100644
index 000000000..5162f9d95
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/object_derive.rs
@@ -0,0 +1,1007 @@
+//! Tests for `#[derive(GraphQLObject)]` macro.
+
+use juniper::{
+    execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription,
+    GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables,
+};
+
+fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
+where
+    Q: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
+{
+    RootNode::new(
+        query_root,
+        EmptyMutation::<C>::new(),
+        EmptySubscription::<C>::new(),
+    )
+}
+
+fn schema_with_scalar<'q, S, C, 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_with_scalar_value(
+        query_root,
+        EmptyMutation::<C>::new(),
+        EmptySubscription::<C>::new(),
+    )
+}
+
+mod trivial {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Human {
+        id: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human { id: "human-32" }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_object() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+        );
+    }
+}
+
+mod raw_field {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Human {
+        r#async: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human {
+                r#async: "human-32",
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                async
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"async": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+                kind
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "Human",
+                    "kind": "OBJECT",
+                    "fields": [{"name": "async"}],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod ignored_field {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Human {
+        id: &'static str,
+        #[allow(dead_code)]
+        #[graphql(ignore)]
+        planet: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human {
+                id: "human-32",
+                planet: "earth",
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_not_field() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{"name": "id"}]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod generic {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Human<B: ?Sized = ()> {
+        id: &'static str,
+        #[graphql(ignore)]
+        _home_planet: B,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human {
+                id: "human-32",
+                _home_planet: (),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod generic_lifetime {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Human<'id, B: ?Sized = ()> {
+        id: &'id str,
+        #[graphql(ignore)]
+        _home_planet: B,
+    }
+
+    struct QueryRoot(String);
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human(&self) -> Human<'_, i32> {
+            Human {
+                id: self.0.as_str(),
+                _home_planet: 32,
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "mars"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod nested_generic_lifetime_async {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Droid<'p, A = ()> {
+        #[graphql(ignore)]
+        _id: A,
+        primary_function: &'p str,
+    }
+
+    #[derive(GraphQLObject)]
+    struct Human<'d, A: Sync = ()> {
+        id: i32,
+        droid: Droid<'d, A>,
+    }
+
+    struct QueryRoot(String);
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human(&self) -> Human<'_, i8> {
+            Human {
+                id: 32,
+                droid: Droid {
+                    _id: 12,
+                    primary_function: self.0.as_str(),
+                },
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            human {
+                id
+                droid {
+                    primaryFunction
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot("mars".into()));
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": 32,
+                    "droid": {
+                        "primaryFunction": "mars",
+                    },
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        for object in &["Human", "Droid"] {
+            let doc = format!(
+                r#"{{
+                    __type(name: "{}") {{
+                        name
+                    }}
+                }}"#,
+                object,
+            );
+
+            let schema = schema(QueryRoot("mars".into()));
+
+            let expected_name: &str = *object;
+            assert_eq!(
+                execute(&doc, None, &schema, &Variables::new(), &()).await,
+                Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])),
+            );
+        }
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    /// Rust docs.\
+    /// Here.
+    #[derive(GraphQLObject)]
+    struct Human {
+        /// Rust `id` docs.
+        /// Here.
+        id: String,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human {
+                id: "human-32".into(),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_doc_comment_as_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                description
+                fields {
+                    description
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "Rust docs. Here.",
+                    "fields": [{"description": "Rust `id` docs.\nHere."}],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod deprecation_from_attr {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct Human {
+        id: String,
+        #[deprecated]
+        a: &'static str,
+        #[deprecated(note = "Use `id`.")]
+        b: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        #[allow(deprecated)]
+        fn human() -> Human {
+            Human {
+                id: "human-32".into(),
+                a: "a",
+                b: "b",
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_fields() {
+        const DOC: &str = r#"{
+            human {
+                a
+                b
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"a": "a", "b": "b"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn deprecates_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                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: "Human") {
+                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 {
+    use super::*;
+
+    /// Rust docs.
+    #[derive(GraphQLObject)]
+    #[graphql(name = "MyHuman", desc = "My human.")]
+    struct Human {
+        /// Rust `id` docs.
+        #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")]
+        #[deprecated(note = "Should be omitted.")]
+        id: String,
+        #[graphql(deprecated)]
+        #[deprecated(note = "Should be omitted.")]
+        a: &'static str,
+        b: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        #[allow(deprecated)]
+        fn human() -> Human {
+            Human {
+                id: "human-32".into(),
+                a: "a",
+                b: "b",
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                myId
+                a
+                b
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {"myId": "human-32", "a": "a", "b": "b"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_name() {
+        const DOC: &str = r#"{
+            __type(name: "MyHuman") {
+                name
+                fields(includeDeprecated: true) {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "MyHuman",
+                    "fields": [
+                        {"name": "myId"},
+                        {"name": "a"},
+                        {"name": "b"},
+                    ],
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_description() {
+        const DOC: &str = r#"{
+            __type(name: "MyHuman") {
+                description
+                fields(includeDeprecated: true) {
+                    name
+                    description
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "My human.",
+                    "fields": [{
+                        "name": "myId",
+                        "description": "My human ID.",
+                    }, {
+                        "name": "a",
+                        "description": None,
+                    }, {
+                        "name": "b",
+                        "description": None,
+                    }],
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_deprecation() {
+        const DOC: &str = r#"{
+            __type(name: "MyHuman") {
+                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 renamed_all_fields {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    #[graphql(rename_all = "none")]
+    struct Human {
+        id: &'static str,
+        home_planet: String,
+        r#async_info: i32,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human {
+                id: "human-32",
+                home_planet: "earth".into(),
+                r#async_info: 3,
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+                home_planet
+                async_info
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"human": {
+                    "id": "human-32",
+                    "home_planet": "earth",
+                    "async_info": 3,
+                }}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_correct_fields_names() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id"},
+                    {"name": "home_planet"},
+                    {"name": "async_info"},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_scalar {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    #[graphql(scalar = DefaultScalarValue)]
+    struct Human {
+        id: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = DefaultScalarValue)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human { id: "human-32" }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+}
+
+mod custom_scalar {
+    use crate::custom_scalar::MyScalarValue;
+
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    #[graphql(scalar = MyScalarValue)]
+    struct Human {
+        id: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(scalar = MyScalarValue)]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human { id: "human-32" }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+}
+
+mod explicit_generic_scalar {
+    use std::marker::PhantomData;
+
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    #[graphql(scalar = S)]
+    struct Human<S: Clone> {
+        id: &'static str,
+        #[graphql(ignore)]
+        _scalar: PhantomData<S>,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human<__S: Clone>() -> Human<__S> {
+            Human {
+                id: "human-32",
+                _scalar: PhantomData,
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    #[graphql(scalar = S: ScalarValue + Clone)]
+    struct Human {
+        id: &'static str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn human() -> Human {
+            Human { id: "human-32" }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])),
+        );
+    }
+}
+
+mod explicit_custom_context {
+    use super::*;
+
+    struct CustomContext(String);
+
+    impl juniper::Context for CustomContext {}
+
+    #[derive(GraphQLObject)]
+    #[graphql(context = CustomContext)]
+    struct Human<'s> {
+        id: &'s str,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object(context = CustomContext)]
+    impl QueryRoot {
+        fn human(ctx: &CustomContext) -> Human<'_> {
+            Human { id: ctx.0.as_str() }
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_fields() {
+        const DOC: &str = r#"{
+            human {
+                id
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &ctx).await,
+            Ok((graphql_value!({"human": {"id": "ctx!"}}), vec![])),
+        );
+    }
+}
diff --git a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
index 087bc91c2..613d6394f 100644
--- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
+++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs
@@ -63,10 +63,7 @@ fn test_scalar_value_custom() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = CustomUserId::meta(&(), &mut registry);
     assert_eq!(meta.name(), Some("MyUserId"));
-    assert_eq!(
-        meta.description(),
-        Some(&"custom description...".to_string())
-    );
+    assert_eq!(meta.description(), Some("custom description..."));
 
     let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
     let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap();
@@ -81,5 +78,5 @@ fn test_scalar_value_custom() {
 fn test_scalar_value_doc_comment() {
     let mut registry: Registry = Registry::new(FnvHashMap::default());
     let meta = IdWithDocComment::meta(&(), &mut registry);
-    assert_eq!(meta.description(), Some(&"The doc comment...".to_string()));
+    assert_eq!(meta.description(), Some("The doc comment..."));
 }
diff --git a/integration_tests/juniper_tests/src/codegen/subscription_attr.rs b/integration_tests/juniper_tests/src/codegen/subscription_attr.rs
new file mode 100644
index 000000000..f2357ca1b
--- /dev/null
+++ b/integration_tests/juniper_tests/src/codegen/subscription_attr.rs
@@ -0,0 +1,1822 @@
+//! Tests for `#[graphql_subscription]` macro.
+
+use std::pin::Pin;
+
+use futures::{future, stream, FutureExt as _, StreamExt as _};
+use juniper::{
+    execute, graphql_object, graphql_subscription, graphql_value, resolve_into_stream,
+    DefaultScalarValue, EmptyMutation, ExecutionError, Executor, FieldError, FieldResult,
+    GraphQLError, GraphQLInputObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, Value,
+    ValuesStream, Variables,
+};
+
+struct Query;
+
+#[graphql_object]
+impl Query {
+    fn empty() -> bool {
+        true
+    }
+}
+
+fn schema<'q, C, Qry, Sub>(
+    query_root: Qry,
+    subscription_root: Sub,
+) -> RootNode<'q, Qry, EmptyMutation<C>, Sub>
+where
+    Qry: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
+    Sub: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
+{
+    RootNode::new(query_root, EmptyMutation::<C>::new(), subscription_root)
+}
+
+fn schema_with_scalar<'q, S, C, Qry, Sub>(
+    query_root: Qry,
+    subscription_root: Sub,
+) -> RootNode<'q, Qry, EmptyMutation<C>, Sub, S>
+where
+    Qry: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
+    Sub: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
+    S: ScalarValue + 'q,
+{
+    RootNode::new_with_scalar_value(query_root, EmptyMutation::<C>::new(), subscription_root)
+}
+
+type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
+
+async fn extract_next<'a, S: ScalarValue>(
+    input: Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError<'a>>,
+) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> {
+    let (stream, errs) = input?;
+    if !errs.is_empty() {
+        return Ok((Value::Null, errs));
+    }
+
+    if let Value::Object(obj) = stream {
+        for (name, mut val) in obj {
+            if let Value::Scalar(ref mut stream) = val {
+                return match stream.next().await {
+                    Some(Ok(val)) => Ok((graphql_value!({ name: val }), vec![])),
+                    Some(Err(e)) => Ok((Value::Null, vec![e])),
+                    None => Ok((Value::Null, vec![])),
+                };
+            }
+        }
+    }
+
+    panic!("Expected to get Value::Object containing a Stream")
+}
+
+mod trivial {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn id() -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready("human-32".to_owned())))
+        }
+
+        // TODO: Make work for `Stream<'_, String>`.
+        async fn home_planet(&self) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready("earth".to_owned())))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_object() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                description
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+        );
+    }
+}
+
+mod raw_method {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn r#my_id() -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        async fn r#async(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("async-32")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_my_id_field() {
+        const DOC: &str = r#"subscription {
+            myId
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"myId": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_async_field() {
+        const DOC: &str = r#"subscription {
+            async
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"async": "async-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+                kind
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "Human",
+                    "kind": "OBJECT",
+                    "fields": [{"name": "myId"}, {"name": "async"}],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod ignored_method {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn id() -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready("human-32".to_owned())))
+        }
+
+        #[allow(dead_code)]
+        #[graphql(ignore)]
+        fn planet() -> &'static str {
+            "earth"
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_not_field() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{"name": "id"}]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod fallible_method {
+    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"}))
+        }
+    }
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn id(&self) -> Result<Stream<'static, String>, CustomError> {
+            Ok(Box::pin(stream::once(future::ready("human-32".to_owned()))))
+        }
+
+        async fn home_planet<__S>() -> FieldResult<Stream<'static, &'static str>, __S> {
+            Ok(Box::pin(stream::once(future::ready("earth"))))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_graphql_type() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+                kind
+                fields {
+                    name
+                    type {
+                        kind
+                        ofType {
+                            name
+                        }
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "Human",
+                    "kind": "OBJECT",
+                    "fields": [{
+                        "name": "id",
+                        "type": {"kind": "NON_NULL", "ofType": {"name": "String"}},
+                    }, {
+                        "name": "homePlanet",
+                        "type": {"kind": "NON_NULL", "ofType": {"name": "String"}},
+                    }]
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod argument {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn id(arg: String) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(arg)))
+        }
+
+        async fn home_planet(
+            &self,
+            r#raw_arg: String,
+            r#async: Option<i32>,
+        ) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(format!(
+                "{},{:?}",
+                r#raw_arg, r#async
+            ))))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id(arg: "human-32")
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet(rawArg: "earth")
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth,None"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_correct_name() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": [{"name": "arg"}]},
+                    {"name": "homePlanet", "args": [{"name": "rawArg"}, {"name": "async"}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    args {
+                        description
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"args": [{"description": None}]},
+                    {"args": [{"description": None}, {"description": None}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_defaults() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    args {
+                        defaultValue
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"args": [{"defaultValue": None}]},
+                    {"args": [{"defaultValue": None}, {"defaultValue": None}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod default_argument {
+    use super::*;
+
+    #[derive(GraphQLInputObject, Debug)]
+    struct Point {
+        x: i32,
+    }
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn id(
+            &self,
+            #[graphql(default)] arg1: i32,
+            #[graphql(default = "second".to_string())] arg2: String,
+            #[graphql(default = true)] r#arg3: bool,
+        ) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(format!(
+                "{}|{}&{}",
+                arg1, arg2, r#arg3
+            ))))
+        }
+
+        async fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> Stream<'static, i32> {
+            Box::pin(stream::once(future::ready(coord.x)))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        let schema = schema(Query, Human);
+
+        for (input, expected) in &[
+            ("subscription { id }", "0|second&true"),
+            ("subscription { id(arg1: 1) }", "1|second&true"),
+            (r#"subscription { id(arg2: "") }"#, "0|&true"),
+            (r#"subscription { id(arg1: 2, arg2: "") }"#, "2|&true"),
+            (
+                r#"subscription { id(arg1: 1, arg2: "", arg3: false) }"#,
+                "1|&false",
+            ),
+        ] {
+            let expected: &str = *expected;
+
+            assert_eq!(
+                resolve_into_stream(*input, None, &schema, &Variables::new(), &())
+                    .then(|s| extract_next(s))
+                    .await,
+                Ok((graphql_value!({ "id": expected }), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_info_field() {
+        let schema = schema(Query, Human);
+
+        for (input, expected) in &[
+            ("subscription { info }", 1),
+            ("subscription { info(coord: { x: 2 }) }", 2),
+        ] {
+            let expected: i32 = *expected;
+
+            assert_eq!(
+                resolve_into_stream(*input, None, &schema, &Variables::new(), &())
+                    .then(|s| extract_next(s))
+                    .await,
+                Ok((graphql_value!({ "info": expected }), vec![])),
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn has_defaults() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    args {
+                        name
+                        defaultValue
+                        type {
+                            name
+                            ofType {
+                                name
+                            }
+                        }
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [{
+                    "args": [{
+                        "name": "arg1",
+                        "defaultValue": "0",
+                        "type": {"name": "Int", "ofType": None},
+                    }, {
+                        "name": "arg2",
+                        "defaultValue": r#""second""#,
+                        "type": {"name": "String", "ofType": None},
+                    }, {
+                        "name": "arg3",
+                        "defaultValue": "true",
+                        "type": {"name": "Boolean", "ofType": None},
+                    }],
+                }, {
+                    "args": [{
+                        "name": "coord",
+                        "defaultValue": "{x: 1}",
+                        "type": {"name": "Point", "ofType": None},
+                    }],
+                }]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod generic {
+    use super::*;
+
+    struct Human<A = (), B: ?Sized = ()> {
+        id: A,
+        _home_planet: B,
+    }
+
+    #[graphql_subscription]
+    impl<B: ?Sized + Sync> Human<i32, B> {
+        async fn id(&self) -> Stream<'static, i32> {
+            Box::pin(stream::once(future::ready(self.id)))
+        }
+    }
+
+    #[graphql_subscription(name = "HumanString")]
+    impl<B: ?Sized + Sync> Human<String, B> {
+        async fn id(&self) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(self.id.to_owned())))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: 34i32,
+                _home_planet: (),
+            },
+        );
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": 34}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: "human-32".to_owned(),
+                _home_planet: (),
+            },
+        );
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: 0i32,
+                _home_planet: (),
+            },
+        );
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod generic_lifetime {
+    use super::*;
+
+    struct Human<'p, A = ()> {
+        id: A,
+        home_planet: &'p str,
+    }
+
+    #[graphql_subscription]
+    impl<'p> Human<'p, i32> {
+        async fn id(&self) -> Stream<'static, i32> {
+            Box::pin(stream::once(future::ready(self.id)))
+        }
+
+        // TODO: Make it work with `Stream<'_, &str>`.
+        async fn planet(&self) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(self.home_planet.to_owned())))
+        }
+    }
+
+    #[graphql_subscription(name = "HumanString")]
+    impl<'id, 'p> Human<'p, &'id str> {
+        // TODO: Make it work with `Stream<'_, &str>`.
+        async fn id(&self) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(self.id.to_owned())))
+        }
+
+        // TODO: Make it work with `Stream<'_, &str>`.
+        async fn planet(&self) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(self.home_planet.to_owned())))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_human_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: 34i32,
+                home_planet: "earth",
+            },
+        );
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": 34}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_planet_field() {
+        const DOC: &str = r#"subscription {
+            planet
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: 34i32,
+                home_planet: "earth",
+            },
+        );
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"planet": "earth"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: "human-32",
+                home_planet: "mars",
+            },
+        );
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_human_string_planet_field() {
+        const DOC: &str = r#"subscription {
+            planet
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: "human-32",
+                home_planet: "mars",
+            },
+        );
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"planet": "mars"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                name
+            }
+        }"#;
+
+        let schema = schema(
+            Query,
+            Human {
+                id: 34i32,
+                home_planet: "earth",
+            },
+        );
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    struct Human;
+
+    /// Rust docs.
+    #[graphql_subscription]
+    impl Human {
+        /// Rust `id` docs.
+        /// Here.
+        async fn id() -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+    }
+
+    #[tokio::test]
+    async fn uses_doc_comment_as_description() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                description
+                fields {
+                    description
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "Rust docs.",
+                    "fields": [{"description": "Rust `id` docs.\nHere."}],
+                }}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod deprecation_from_attr {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        async fn id() -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        #[deprecated]
+        async fn a(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("a")))
+        }
+
+        #[deprecated(note = "Use `id`.")]
+        async fn b(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("b")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_a_field() {
+        const DOC: &str = r#"subscription {
+            a
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"a": "a"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_b_field() {
+        const DOC: &str = r#"subscription {
+            b
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"b": "b"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn deprecates_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields(includeDeprecated: true) {
+                    name
+                    isDeprecated
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        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: "Human") {
+                fields(includeDeprecated: true) {
+                    name
+                    deprecationReason
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        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 {
+    use super::*;
+
+    struct Human;
+
+    /// Rust docs.
+    #[graphql_subscription(name = "MyHuman", desc = "My human.")]
+    impl Human {
+        /// Rust `id` docs.
+        #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")]
+        #[deprecated(note = "Should be omitted.")]
+        async fn id(
+            #[graphql(name = "myName", desc = "My argument.", default)] _n: String,
+        ) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        #[graphql(deprecated)]
+        #[deprecated(note = "Should be omitted.")]
+        async fn a(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("a")))
+        }
+
+        async fn b(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("b")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_id_field() {
+        const DOC: &str = r#"subscription {
+            myId
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"myId": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_deprecated_a_field() {
+        const DOC: &str = r#"subscription {
+            a
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"a": "a"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_b_field() {
+        const DOC: &str = r#"subscription {
+            b
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"b": "b"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_custom_name() {
+        const DOC: &str = r#"{
+            __type(name: "MyHuman") {
+                name
+                fields(includeDeprecated: true) {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "name": "MyHuman",
+                    "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: "MyHuman") {
+                description
+                fields(includeDeprecated: true) {
+                    name
+                    description
+                    args {
+                        description
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "My human.",
+                    "fields": [{
+                        "name": "myId",
+                        "description": "My human 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: "MyHuman") {
+                fields(includeDeprecated: true) {
+                    name
+                    isDeprecated
+                    deprecationReason
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        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 renamed_all_fields_and_args {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription(rename_all = "none")]
+    impl Human {
+        async fn id() -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        async fn home_planet(&self, planet_name: String) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(planet_name)))
+        }
+
+        async fn r#async_info(r#my_num: i32) -> Stream<'static, i32> {
+            Box::pin(stream::once(future::ready(r#my_num)))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            home_planet(planet_name: "earth")
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"home_planet": "earth"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_async_info_field() {
+        const DOC: &str = r#"subscription {
+            async_info(my_num: 3)
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"async_info": 3}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_correct_fields_and_args_names() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": []},
+                    {"name": "home_planet", "args": [{"name": "planet_name"}]},
+                    {"name": "async_info", "args": [{"name": "my_num"}]},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod explicit_scalar {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription(scalar = DefaultScalarValue)]
+    impl Human {
+        async fn id(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        async fn home_planet() -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("earth")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth"}), vec![])),
+        );
+    }
+}
+
+mod custom_scalar {
+    use crate::custom_scalar::MyScalarValue;
+
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription(scalar = MyScalarValue)]
+    impl Human {
+        async fn id(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        async fn home_planet() -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("earth")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _, _>(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet
+        }"#;
+
+        let schema = schema_with_scalar::<MyScalarValue, _, _, _>(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth"}), vec![])),
+        );
+    }
+}
+
+mod explicit_generic_scalar {
+    use std::marker::PhantomData;
+
+    use super::*;
+
+    struct Human<S>(PhantomData<S>);
+
+    #[graphql_subscription(scalar = S)]
+    impl<S: ScalarValue> Human<S> {
+        async fn id(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        async fn home_planet(_executor: &Executor<'_, '_, (), S>) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("earth")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human::<DefaultScalarValue>(PhantomData));
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet
+        }"#;
+
+        let schema = schema(Query, Human::<DefaultScalarValue>(PhantomData));
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth"}), vec![])),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription(scalar = S: ScalarValue + Clone)]
+    impl Human {
+        async fn id(&self) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human-32")))
+        }
+
+        async fn home_planet<S>(
+            _executor: &Executor<'_, '_, (), S>,
+        ) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("earth")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "human-32"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_home_planet_field() {
+        const DOC: &str = r#"subscription {
+            homePlanet
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"homePlanet": "earth"}), vec![])),
+        );
+    }
+}
+
+mod explicit_custom_context {
+    use super::*;
+
+    struct CustomContext(String);
+
+    impl juniper::Context for CustomContext {}
+
+    struct QueryRoot;
+
+    #[graphql_object(context = CustomContext)]
+    impl QueryRoot {
+        fn empty() -> bool {
+            true
+        }
+    }
+
+    struct Human;
+
+    #[graphql_subscription(context = CustomContext)]
+    impl Human {
+        // TODO: Make work for `Stream<'c, String>`.
+        async fn id<'c>(&self, context: &'c CustomContext) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(context.0.clone())))
+        }
+
+        // TODO: Make work for `Stream<'_, String>`.
+        async fn info(_ctx: &()) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human being")))
+        }
+
+        async fn more(#[graphql(context)] custom: &CustomContext) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(custom.0.clone())))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(QueryRoot, Human);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx)
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "ctx!"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_info_field() {
+        const DOC: &str = r#"subscription {
+            info
+        }"#;
+
+        let schema = schema(QueryRoot, Human);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx)
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"info": "human being"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_more_field() {
+        const DOC: &str = r#"subscription {
+            more
+        }"#;
+
+        let schema = schema(QueryRoot, Human);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx)
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"more": "ctx!"}), vec![])),
+        );
+    }
+}
+
+mod inferred_custom_context_from_field {
+    use super::*;
+
+    struct CustomContext(String);
+
+    impl juniper::Context for CustomContext {}
+
+    struct QueryRoot;
+
+    #[graphql_object(context = CustomContext)]
+    impl QueryRoot {
+        fn empty() -> bool {
+            true
+        }
+    }
+
+    struct Human;
+
+    #[graphql_subscription]
+    impl Human {
+        // TODO: Make work for `Stream<'c, String>`.
+        async fn id<'c>(&self, context: &'c CustomContext) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(context.0.clone())))
+        }
+
+        // TODO: Make work for `Stream<'_, String>`.
+        async fn info(_ctx: &()) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("human being")))
+        }
+
+        async fn more(#[graphql(context)] custom: &CustomContext) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(custom.0.clone())))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(QueryRoot, Human);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx)
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "ctx!"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_info_field() {
+        const DOC: &str = r#"subscription {
+            info
+        }"#;
+
+        let schema = schema(QueryRoot, Human);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx)
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"info": "human being"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_more_field() {
+        const DOC: &str = r#"subscription {
+            more
+        }"#;
+
+        let schema = schema(QueryRoot, Human);
+        let ctx = CustomContext("ctx!".into());
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx)
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"more": "ctx!"}), vec![])),
+        );
+    }
+}
+
+mod executor {
+    use juniper::LookAheadMethods as _;
+
+    use super::*;
+
+    struct Human;
+
+    #[graphql_subscription(scalar = S: ScalarValue)]
+    impl Human {
+        // TODO: Make work for `Stream<'e, &'e str>`.
+        async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> Stream<'static, String>
+        where
+            S: ScalarValue,
+        {
+            Box::pin(stream::once(future::ready(
+                executor.look_ahead().field_name().to_owned(),
+            )))
+        }
+
+        async fn info<S>(
+            &self,
+            arg: String,
+            #[graphql(executor)] _another: &Executor<'_, '_, (), S>,
+        ) -> Stream<'static, String> {
+            Box::pin(stream::once(future::ready(arg)))
+        }
+
+        // TODO: Make work for `Stream<'e, &'e str>`.
+        async fn info2<'e, S>(
+            _executor: &'e Executor<'_, '_, (), S>,
+        ) -> Stream<'static, &'static str> {
+            Box::pin(stream::once(future::ready("no info")))
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves_id_field() {
+        const DOC: &str = r#"subscription {
+            id
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"id": "id"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_info_field() {
+        const DOC: &str = r#"subscription {
+            info(arg: "input!")
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"info": "input!"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_info2_field() {
+        const DOC: &str = r#"subscription {
+            info2
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
+                .then(|s| extract_next(s))
+                .await,
+            Ok((graphql_value!({"info2": "no info"}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn not_arg() {
+        const DOC: &str = r#"{
+            __type(name: "Human") {
+                fields {
+                    name
+                    args {
+                        name
+                    }
+                }
+            }
+        }"#;
+
+        let schema = schema(Query, Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"__type": {"fields": [
+                    {"name": "id", "args": []},
+                    {"name": "info", "args": [{"name": "arg"}]},
+                    {"name": "info2", "args": []},
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs
index 52820fb6a..4d831ba9c 100644
--- a/integration_tests/juniper_tests/src/codegen/union_attr.rs
+++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs
@@ -660,6 +660,184 @@ mod custom_scalar {
     }
 }
 
+mod explicit_generic_scalar {
+    use super::*;
+
+    #[graphql_union(scalar = S)]
+    trait Character<S: ScalarValue> {
+        fn as_human(&self) -> Option<&Human> {
+            None
+        }
+        fn as_droid(&self) -> Option<&Droid> {
+            None
+        }
+    }
+
+    impl<S: ScalarValue> Character<S> for Human {
+        fn as_human(&self) -> Option<&Human> {
+            Some(&self)
+        }
+    }
+
+    impl<S: ScalarValue> Character<S> for Droid {
+        fn as_droid(&self) -> Option<&Droid> {
+            Some(&self)
+        }
+    }
+
+    type DynCharacter<'a, S> = dyn Character<S> + Send + Sync + 'a;
+
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character<__S: ScalarValue>(&self) -> Box<DynCharacter<'_, __S>> {
+            let ch: Box<DynCharacter<'_, _>> = 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
+        }
+    }
+
+    const DOC: &str = r#"{
+        character {
+            ... on Human {
+                humanId: id
+                homePlanet
+            }
+            ... on Droid {
+                droidId: id
+                primaryFunction
+            }
+        }
+    }"#;
+
+    #[tokio::test]
+    async fn resolves_human() {
+        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() {
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    #[graphql_union(scalar = S: ScalarValue + Clone)]
+    trait Character {
+        fn as_human(&self) -> Option<&Human> {
+            None
+        }
+        fn as_droid(&self) -> Option<&Droid> {
+            None
+        }
+    }
+
+    impl Character for Human {
+        fn as_human(&self) -> Option<&Human> {
+            Some(&self)
+        }
+    }
+
+    impl Character for Droid {
+        fn as_droid(&self) -> Option<&Droid> {
+            Some(&self)
+        }
+    }
+
+    type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
+
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> Box<DynCharacter<'_>> {
+            let ch: Box<DynCharacter<'_>> = 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
+        }
+    }
+
+    const DOC: &str = r#"{
+        character {
+            ... on Human {
+                humanId: id
+                homePlanet
+            }
+            ... on Droid {
+                droidId: id
+                primaryFunction
+            }
+        }
+    }"#;
+
+    #[tokio::test]
+    async fn resolves_human() {
+        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() {
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
 mod explicit_custom_context {
     use super::*;
 
diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs
index ed90741bd..6fd203649 100644
--- a/integration_tests/juniper_tests/src/codegen/union_derive.rs
+++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs
@@ -284,6 +284,101 @@ mod generic_enum {
     }
 }
 
+/* TODO: make it work
+mod generic_lifetime_enum {
+    use super::*;
+
+    #[derive(GraphQLObject)]
+    struct LifetimeHuman<'id> {
+        id: &'id str,
+    }
+
+    #[derive(GraphQLObject)]
+    struct GenericDroid<B = ()> {
+        id: String,
+        #[graphql(ignore)]
+        _t: PhantomData<B>,
+    }
+
+    #[derive(GraphQLUnion)]
+    enum Character<'id, B = ()> {
+        A(LifetimeHuman<'id>),
+        B(GenericDroid<B>),
+    }
+
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> Character<'_> {
+            match self {
+                Self::Human => Character::A(LifetimeHuman { id: "human-32" }),
+                Self::Droid => Character::B(GenericDroid {
+                    id: "droid-99".to_string(),
+                    _t: PhantomData,
+                }),
+            }
+        }
+    }
+
+    const DOC: &str = r#"{
+        character {
+            ... on LifetimeHuman {
+                humanId: id
+            }
+            ... on GenericDroid {
+                droidId: id
+            }
+        }
+    }"#;
+
+    #[tokio::test]
+    async fn resolves_human() {
+        let schema = schema(QueryRoot::Human);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"humanId": "human-32"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn resolves_droid() {
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"droidId": "droid-99"}}),
+                vec![],
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name_without_type_params() {
+        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![])),
+        );
+    }
+}
+*/
+
 mod description_from_doc_comments {
     use super::*;
 
@@ -571,6 +666,150 @@ mod custom_scalar {
     }
 }
 
+mod explicit_generic_scalar {
+    use super::*;
+
+    #[derive(GraphQLUnion)]
+    #[graphql(scalar = S)]
+    enum Character<S: ScalarValue> {
+        A(Human),
+        B(Droid),
+        #[graphql(ignore)]
+        _P(PhantomData<S>),
+    }
+
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character<__S: ScalarValue>(&self) -> Character<__S> {
+            match self {
+                Self::Human => Character::A(Human {
+                    id: "human-32".to_string(),
+                    home_planet: "earth".to_string(),
+                }),
+                Self::Droid => Character::B(Droid {
+                    id: "droid-99".to_string(),
+                    primary_function: "run".to_string(),
+                }),
+            }
+        }
+    }
+
+    const DOC: &str = r#"{
+        character {
+            ... on Human {
+                humanId: id
+                homePlanet
+            }
+            ... on Droid {
+                droidId: id
+                primaryFunction
+            }
+        }
+    }"#;
+
+    #[tokio::test]
+    async fn resolves_human() {
+        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() {
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod bounded_generic_scalar {
+    use super::*;
+
+    #[derive(GraphQLUnion)]
+    #[graphql(scalar = S: ScalarValue + Clone)]
+    enum Character {
+        A(Human),
+        B(Droid),
+    }
+
+    enum QueryRoot {
+        Human,
+        Droid,
+    }
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn character(&self) -> Character {
+            match self {
+                Self::Human => Character::A(Human {
+                    id: "human-32".to_string(),
+                    home_planet: "earth".to_string(),
+                }),
+                Self::Droid => Character::B(Droid {
+                    id: "droid-99".to_string(),
+                    primary_function: "run".to_string(),
+                }),
+            }
+        }
+    }
+
+    const DOC: &str = r#"{
+        character {
+            ... on Human {
+                humanId: id
+                homePlanet
+            }
+            ... on Droid {
+                droidId: id
+                primaryFunction
+            }
+        }
+    }"#;
+
+    #[tokio::test]
+    async fn resolves_human() {
+        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() {
+        let schema = schema(QueryRoot::Droid);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &()).await,
+            Ok((
+                graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
+                vec![],
+            )),
+        );
+    }
+}
+
 mod custom_context {
     use super::*;
 
@@ -1124,8 +1363,8 @@ mod trivial_struct {
     #[derive(GraphQLUnion)]
     #[graphql(context = Database)]
     #[graphql(
-    on Human = Character::as_human,
-    on Droid = Character::as_droid,
+        on Human = Character::as_human,
+        on Droid = Character::as_droid,
     )]
     struct Character {
         id: String,
@@ -1227,6 +1466,75 @@ mod trivial_struct {
             )),
         );
     }
+
+    #[tokio::test]
+    async fn is_graphql_union() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Human);
+        let db = Database {
+            human: Some(Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }),
+            droid: None,
+        };
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &db).await,
+            Ok((graphql_value!({"__type": {"kind": "UNION"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Character") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot::Human);
+        let db = Database {
+            human: Some(Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }),
+            droid: None,
+        };
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &db).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);
+        let db = Database {
+            human: Some(Human {
+                id: "human-32".to_string(),
+                home_planet: "earth".to_string(),
+            }),
+            droid: None,
+        };
+
+        assert_eq!(
+            execute(DOC, None, &schema, &Variables::new(), &db).await,
+            Ok((graphql_value!({"__type": {"description": None}}), vec![])),
+        );
+    }
 }
 
 mod generic_struct {
@@ -1473,6 +1781,8 @@ mod full_featured_struct {
     }
 }
 
+/// Checks that union with boxed variants resolves okay.
+/// See [#845](https://github.com/graphql-rust/juniper/issues/845) for details.
 mod issue_845 {
     use std::sync::Arc;
 
diff --git a/integration_tests/juniper_tests/src/explicit_null.rs b/integration_tests/juniper_tests/src/explicit_null.rs
index 3ade4b528..0349f2621 100644
--- a/integration_tests/juniper_tests/src/explicit_null.rs
+++ b/integration_tests/juniper_tests/src/explicit_null.rs
@@ -1,4 +1,7 @@
-use juniper::*;
+use juniper::{
+    graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLInputObject,
+    InputValue, Nullable,
+};
 
 pub struct Context;
 
@@ -6,12 +9,12 @@ impl juniper::Context for Context {}
 
 pub struct Query;
 
-#[derive(juniper::GraphQLInputObject)]
+#[derive(GraphQLInputObject)]
 struct ObjectInput {
     field: Nullable<i32>,
 }
 
-#[graphql_object(Context=Context)]
+#[graphql_object(context = Context)]
 impl Query {
     fn is_explicit_null(arg: Nullable<i32>) -> bool {
         arg.is_explicit_null()
@@ -26,40 +29,38 @@ type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>, EmptySub
 
 #[tokio::test]
 async fn explicit_null() {
-    let ctx = Context;
-
     let query = r#"
-    query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) {
-        literalOneIsExplicitNull: isExplicitNull(arg: 1)
-        literalNullIsExplicitNull: isExplicitNull(arg: null)
-        noArgIsExplicitNull: isExplicitNull
-        literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1})
-        literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null})
-        noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {})
-        emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj)
-        literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj)
-    }
+        query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) {
+            literalOneIsExplicitNull: isExplicitNull(arg: 1)
+            literalNullIsExplicitNull: isExplicitNull(arg: null)
+            noArgIsExplicitNull: isExplicitNull
+            literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1})
+            literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null})
+            noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {})
+            emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj)
+            literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj)
+        }
     "#;
 
+    let schema = &Schema::new(
+        Query,
+        EmptyMutation::<Context>::new(),
+        EmptySubscription::<Context>::new(),
+    );
+    let vars = [
+        ("emptyObj".to_string(), InputValue::Object(vec![])),
+        (
+            "literalNullObj".to_string(),
+            InputValue::object(vec![("field", InputValue::null())].into_iter().collect()),
+        ),
+    ];
+
     let (data, errors) = juniper::execute(
         query,
         None,
-        &Schema::new(
-            Query,
-            EmptyMutation::<Context>::new(),
-            EmptySubscription::<Context>::new(),
-        ),
-        &[
-            ("emptyObj".to_string(), InputValue::Object(vec![])),
-            (
-                "literalNullObj".to_string(),
-                InputValue::object(vec![("field", InputValue::null())].into_iter().collect()),
-            ),
-        ]
-        .iter()
-        .cloned()
-        .collect(),
-        &ctx,
+        &schema,
+        &vars.iter().cloned().collect(),
+        &Context,
     )
     .await
     .unwrap();
diff --git a/integration_tests/juniper_tests/src/issue_371.rs b/integration_tests/juniper_tests/src/issue_371.rs
index d4085e60d..571a1587e 100644
--- a/integration_tests/juniper_tests/src/issue_371.rs
+++ b/integration_tests/juniper_tests/src/issue_371.rs
@@ -1,7 +1,12 @@
-// Original author of this test is <https://github.com/davidpdrsn>.
+//! Checks that `executor.look_ahead().field_name()` is correct in presence of
+//! multiple query fields.
+//! See [#371](https://github.com/graphql-rust/juniper/issues/371) for details.
+//!
+//! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn).
 
 use juniper::{
-    graphql_object, EmptyMutation, EmptySubscription, LookAheadMethods as _, RootNode, Variables,
+    graphql_object, EmptyMutation, EmptySubscription, Executor, LookAheadMethods as _, RootNode,
+    ScalarValue, Variables,
 };
 
 pub struct Context;
@@ -12,14 +17,14 @@ pub struct Query;
 
 #[graphql_object(context = Context)]
 impl Query {
-    fn users(exec: &Executor) -> Vec<User> {
-        let lh = exec.look_ahead();
+    fn users<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec<User> {
+        let lh = executor.look_ahead();
         assert_eq!(lh.field_name(), "users");
         vec![User]
     }
 
-    fn countries(exec: &Executor) -> Vec<Country> {
-        let lh = exec.look_ahead();
+    fn countries<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec<Country> {
+        let lh = executor.look_ahead();
         assert_eq!(lh.field_name(), "countries");
         vec![Country]
     }
@@ -49,98 +54,54 @@ type Schema = RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription
 
 #[tokio::test]
 async fn users() {
-    let ctx = Context;
-
-    let query = r#"{ users { id } }"#;
-
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(
-            Query,
-            EmptyMutation::<Context>::new(),
-            EmptySubscription::<Context>::new(),
-        ),
-        &juniper::Variables::new(),
-        &ctx,
-    )
-    .await
-    .unwrap();
+    let query = "{ users { id } }";
+
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
 }
 
 #[tokio::test]
 async fn countries() {
-    let ctx = Context;
-
-    let query = r#"{ countries { id } }"#;
+    let query = "{ countries { id } }";
 
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &juniper::Variables::new(),
-        &ctx,
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
 }
 
 #[tokio::test]
 async fn both() {
-    let ctx = Context;
-
-    let query = r#"
-    {
+    let query = "{
         countries { id }
         users { id }
-    }
-    "#;
-
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(
-            Query,
-            EmptyMutation::<Context>::new(),
-            EmptySubscription::<Context>::new(),
-        ),
-        &Variables::new(),
-        &ctx,
-    )
-    .await
-    .unwrap();
+    }";
+
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
 }
 
 #[tokio::test]
 async fn both_in_different_order() {
-    let ctx = Context;
-
-    let query = r#"
-    {
+    let query = "{
         users { id }
         countries { id }
-    }
-    "#;
-
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(
-            Query,
-            EmptyMutation::<Context>::new(),
-            EmptySubscription::<Context>::new(),
-        ),
-        &Variables::new(),
-        &ctx,
-    )
-    .await
-    .unwrap();
+    }";
+
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
 }
diff --git a/integration_tests/juniper_tests/src/issue_372.rs b/integration_tests/juniper_tests/src/issue_372.rs
index a78cf0d1a..661c90db6 100644
--- a/integration_tests/juniper_tests/src/issue_372.rs
+++ b/integration_tests/juniper_tests/src/issue_372.rs
@@ -1,3 +1,6 @@
+//! Checks that `__typename` field queries okay on root types.
+//! See [#372](https://github.com/graphql-rust/juniper/issues/372) for details.
+
 use futures::{stream, StreamExt as _};
 use juniper::{graphql_object, graphql_subscription, graphql_value, RootNode, Value, Variables};
 
diff --git a/integration_tests/juniper_tests/src/issue_398.rs b/integration_tests/juniper_tests/src/issue_398.rs
index 5a42ac238..0d5557411 100644
--- a/integration_tests/juniper_tests/src/issue_398.rs
+++ b/integration_tests/juniper_tests/src/issue_398.rs
@@ -1,13 +1,18 @@
-// Original author of this test is <https://github.com/davidpdrsn>.
+//! Checks that `executor.look_ahead()` on a fragment with nested type works okay.
+//! See [#398](https://github.com/graphql-rust/juniper/issues/398) for details.
+//!
+//! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn).
 
-use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode, Variables};
+use juniper::{
+    graphql_object, EmptyMutation, EmptySubscription, Executor, RootNode, ScalarValue, Variables,
+};
 
 struct Query;
 
 #[graphql_object]
 impl Query {
-    fn users(executor: &Executor) -> Vec<User> {
-        // This doesn't cause a panic
+    fn users<S: ScalarValue>(executor: &Executor<'_, '_, (), S>) -> Vec<User> {
+        // This doesn't cause a panic.
         executor.look_ahead();
 
         vec![User {
@@ -22,7 +27,7 @@ struct User {
 
 #[graphql_object]
 impl User {
-    fn country(&self, executor: &Executor) -> &Country {
+    fn country<S: ScalarValue>(&self, executor: &Executor<'_, '_, (), S>) -> &Country {
         // This panics!
         executor.look_ahead();
 
@@ -44,7 +49,7 @@ impl Country {
 type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
 
 #[tokio::test]
-async fn test_lookahead_from_fragment_with_nested_type() {
+async fn lookahead_from_fragment_with_nested_type() {
     let _ = juniper::execute(
         r#"
             query Query {
diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/integration_tests/juniper_tests/src/issue_407.rs
index 6b4e79fb2..11c6075c9 100644
--- a/integration_tests/juniper_tests/src/issue_407.rs
+++ b/integration_tests/juniper_tests/src/issue_407.rs
@@ -1,4 +1,9 @@
-use juniper::*;
+//! Checks that using a fragment of an implementation in an interface works okay.
+//! See [#407](https://github.com/graphql-rust/juniper/issues/407) for details.
+
+use juniper::{
+    graphql_interface, graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, Variables,
+};
 
 struct Query;
 
@@ -53,7 +58,7 @@ impl Query {
 type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
 
 #[tokio::test]
-async fn test_fragments_in_interface() {
+async fn fragments_in_interface() {
     let query = r#"
         query Query {
             characters {
@@ -71,30 +76,19 @@ async fn test_fragments_in_interface() {
         }
     "#;
 
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
     assert_eq!(errors.len(), 0);
 
-    let (_, errors) = juniper::execute_sync(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
     assert_eq!(errors.len(), 0);
 }
 
 #[tokio::test]
-async fn test_inline_fragments_in_interface() {
+async fn inline_fragments_in_interface() {
     let query = r#"
         query Query {
             characters {
@@ -116,24 +110,13 @@ async fn test_inline_fragments_in_interface() {
         }
     "#;
 
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
     assert_eq!(errors.len(), 0);
 
-    let (_, errors) = juniper::execute_sync(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
     assert_eq!(errors.len(), 0);
 }
diff --git a/integration_tests/juniper_tests/src/issue_500.rs b/integration_tests/juniper_tests/src/issue_500.rs
index 7dbb595fd..221b1720e 100644
--- a/integration_tests/juniper_tests/src/issue_500.rs
+++ b/integration_tests/juniper_tests/src/issue_500.rs
@@ -1,10 +1,13 @@
-use juniper::*;
+//! Checks that using nested fragments works okay.
+//! See [#500](https://github.com/graphql-rust/juniper/issues/500) for details.
+
+use juniper::{graphql_object, EmptyMutation, EmptySubscription, Executor, ScalarValue, Variables};
 
 struct Query;
 
-#[juniper::graphql_object]
+#[graphql_object]
 impl Query {
-    fn users(executor: &Executor) -> Vec<User> {
+    fn users<S: ScalarValue>(executor: &Executor<'_, '_, (), S>) -> Vec<User> {
         executor.look_ahead();
 
         vec![User {
@@ -19,9 +22,9 @@ struct User {
     city: City,
 }
 
-#[juniper::graphql_object]
+#[graphql_object]
 impl User {
-    fn city(&self, executor: &Executor) -> &City {
+    fn city<S: ScalarValue>(&self, executor: &Executor<'_, '_, (), S>) -> &City {
         executor.look_ahead();
         &self.city
     }
@@ -31,9 +34,9 @@ struct City {
     country: Country,
 }
 
-#[juniper::graphql_object]
+#[graphql_object]
 impl City {
-    fn country(&self, executor: &Executor) -> &Country {
+    fn country<S: ScalarValue>(&self, executor: &Executor<'_, '_, (), S>) -> &Country {
         executor.look_ahead();
         &self.country
     }
@@ -43,7 +46,7 @@ struct Country {
     id: i32,
 }
 
-#[juniper::graphql_object]
+#[graphql_object]
 impl Country {
     fn id(&self) -> i32 {
         self.id
@@ -53,7 +56,7 @@ impl Country {
 type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
 
 #[tokio::test]
-async fn test_nested_fragments() {
+async fn nested_fragments() {
     let query = r#"
         query Query {
             users {
@@ -78,15 +81,10 @@ async fn test_nested_fragments() {
         }
     "#;
 
-    let (_, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+    let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
 }
diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/integration_tests/juniper_tests/src/issue_798.rs
index f3ec4d700..a8f7ec0de 100644
--- a/integration_tests/juniper_tests/src/issue_798.rs
+++ b/integration_tests/juniper_tests/src/issue_798.rs
@@ -1,3 +1,6 @@
+//! Checks that interface field resolves okay on a union.
+//! See [#798](https://github.com/graphql-rust/juniper/issues/798) for details.
+
 use juniper::{
     graphql_interface, graphql_object, graphql_value, EmptyMutation, EmptySubscription,
     GraphQLObject, GraphQLUnion, RootNode, Variables,
@@ -64,10 +67,10 @@ impl Query {
     }
 }
 
-type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
+type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>;
 
 #[tokio::test]
-async fn test_interface_inline_fragment_on_union() {
+async fn interface_inline_fragment_on_union() {
     let query = r#"
         query Query {
             field {
@@ -85,15 +88,10 @@ async fn test_interface_inline_fragment_on_union() {
         }
     "#;
 
-    let (res, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new());
+    let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
@@ -107,14 +105,9 @@ async fn test_interface_inline_fragment_on_union() {
         }),
     );
 
-    let (res, errors) = juniper::execute_sync(
-        query,
-        None,
-        &Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let schema = Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new());
+    let (res, errors) =
+        juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
@@ -130,7 +123,7 @@ async fn test_interface_inline_fragment_on_union() {
 }
 
 #[tokio::test]
-async fn test_interface_fragment_on_union() {
+async fn interface_fragment_on_union() {
     let query = r#"
         query Query {
             field {
@@ -150,15 +143,10 @@ async fn test_interface_fragment_on_union() {
         }
     "#;
 
-    let (res, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new());
+    let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
@@ -172,14 +160,9 @@ async fn test_interface_fragment_on_union() {
         }),
     );
 
-    let (res, errors) = juniper::execute_sync(
-        query,
-        None,
-        &Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let schema = Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new());
+    let (res, errors) =
+        juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
diff --git a/integration_tests/juniper_tests/src/issue_914.rs b/integration_tests/juniper_tests/src/issue_914.rs
index be15514fb..aa9b86c38 100644
--- a/integration_tests/juniper_tests/src/issue_914.rs
+++ b/integration_tests/juniper_tests/src/issue_914.rs
@@ -1,4 +1,7 @@
-use juniper::*;
+//! Checks that multiple fragments on sub types don't override each other.
+//! See [#914](https://github.com/graphql-rust/juniper/issues/914) for details.
+
+use juniper::{graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, Variables};
 
 struct Query;
 
@@ -32,7 +35,7 @@ impl Query {
 type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
 
 #[tokio::test]
-async fn test_fragments_with_nested_objects_dont_override_previous_selections() {
+async fn fragments_with_nested_objects_dont_override_previous_selections() {
     let query = r#"
         query Query {
             foo {
@@ -72,25 +75,15 @@ async fn test_fragments_with_nested_objects_dont_override_previous_selections()
         }
     "#;
 
-    let (async_value, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+
+    let (async_value, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
     assert_eq!(errors.len(), 0);
 
-    let (sync_value, errors) = juniper::execute_sync(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let (sync_value, errors) =
+        juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
     assert_eq!(errors.len(), 0);
 
     assert_eq!(async_value, sync_value);
diff --git a/integration_tests/juniper_tests/src/issue_922.rs b/integration_tests/juniper_tests/src/issue_922.rs
index 35de87faf..b99803fb3 100644
--- a/integration_tests/juniper_tests/src/issue_922.rs
+++ b/integration_tests/juniper_tests/src/issue_922.rs
@@ -1,8 +1,14 @@
-use juniper::*;
+//! Checks that fields on interface fragment spreads resolve okay.
+//! See [#922](https://github.com/graphql-rust/juniper/issues/922) for details.
+
+use juniper::{
+    graphql_interface, graphql_object, graphql_value, EmptyMutation, EmptySubscription,
+    GraphQLObject, Variables,
+};
 
 struct Query;
 
-#[juniper::graphql_object]
+#[graphql_object]
 impl Query {
     fn characters() -> Vec<CharacterValue> {
         vec![
@@ -18,21 +24,21 @@ impl Query {
     }
 }
 
-#[juniper::graphql_interface(for = [Human, Droid])]
+#[graphql_interface(for = [Human, Droid])]
 trait Character {
     fn id(&self) -> i32;
 
     fn name(&self) -> String;
 }
 
-#[derive(juniper::GraphQLObject)]
+#[derive(GraphQLObject)]
 #[graphql(impl = CharacterValue)]
 struct Human {
     pub id: i32,
     pub name: String,
 }
 
-#[juniper::graphql_interface]
+#[graphql_interface]
 impl Character for Human {
     fn id(&self) -> i32 {
         self.id
@@ -43,14 +49,14 @@ impl Character for Human {
     }
 }
 
-#[derive(juniper::GraphQLObject)]
+#[derive(GraphQLObject)]
 #[graphql(impl = CharacterValue)]
 struct Droid {
     pub id: i32,
     pub name: String,
 }
 
-#[juniper::graphql_interface]
+#[graphql_interface]
 impl Character for Droid {
     fn id(&self) -> i32 {
         self.id
@@ -61,10 +67,10 @@ impl Character for Droid {
     }
 }
 
-type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
+type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
 
 #[tokio::test]
-async fn test_fragment_on_interface() {
+async fn fragment_on_interface() {
     let query = r#"
         query Query {
             characters {
@@ -85,15 +91,11 @@ async fn test_fragment_on_interface() {
         }
     "#;
 
-    let (res, errors) = juniper::execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+
+    let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
@@ -106,14 +108,8 @@ async fn test_fragment_on_interface() {
         }),
     );
 
-    let (res, errors) = juniper::execute_sync(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let (res, errors) =
+        juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
diff --git a/integration_tests/juniper_tests/src/issue_925.rs b/integration_tests/juniper_tests/src/issue_925.rs
index 45180262e..435cb2a7b 100644
--- a/integration_tests/juniper_tests/src/issue_925.rs
+++ b/integration_tests/juniper_tests/src/issue_925.rs
@@ -1,8 +1,14 @@
-use juniper::*;
+//! Checks that `FieldError` doesn't lose its extensions while being implicitly
+//! converted from user defined subscription errors.
+//! See [#925](https://github.com/graphql-rust/juniper/issues/925) for details.
 
 use futures::stream::BoxStream;
+use juniper::{
+    graphql_object, graphql_subscription, graphql_value, EmptyMutation, FieldError, GraphQLObject,
+    IntoFieldError, Object, ScalarValue, Value, Variables,
+};
 
-#[derive(juniper::GraphQLObject)]
+#[derive(GraphQLObject)]
 struct User {
     name: String,
 }
@@ -33,17 +39,17 @@ fn users_stream() -> Result<BoxStream<'static, User>, Error> {
 
 struct Query;
 
-#[juniper::graphql_object]
+#[graphql_object]
 impl Query {
     fn users() -> Vec<User> {
         vec![]
     }
 }
 
-type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, SubscriptionsRoot>;
+type Schema = juniper::RootNode<'static, Query, EmptyMutation, SubscriptionsRoot>;
 
 #[tokio::test]
-async fn test_error_extensions() {
+async fn error_extensions() {
     let sub = r#"
         subscription Users {
             users {
@@ -52,18 +58,13 @@ async fn test_error_extensions() {
         }
     "#;
 
-    let (_, errors) = juniper::resolve_into_stream(
-        sub,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), SubscriptionsRoot),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), SubscriptionsRoot);
+    let (_, errors) = juniper::resolve_into_stream(sub, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
 
     assert_eq!(
         errors.first().unwrap().error().extensions(),
-        &graphql_value!({ "a": 42 })
+        &graphql_value!({ "a": 42 }),
     );
 }
diff --git a/integration_tests/juniper_tests/src/issue_945.rs b/integration_tests/juniper_tests/src/issue_945.rs
index 763fcd69a..9d8c0db85 100644
--- a/integration_tests/juniper_tests/src/issue_945.rs
+++ b/integration_tests/juniper_tests/src/issue_945.rs
@@ -1,4 +1,10 @@
-use juniper::*;
+//! Checks that spreading untyped union fragment work okay.
+//! See [#945](https://github.com/graphql-rust/juniper/issues/945) for details.
+
+use juniper::{
+    graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLUnion,
+    Variables,
+};
 
 struct Query;
 
@@ -34,10 +40,10 @@ struct Droid {
     pub sensor_color: String,
 }
 
-type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
+type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
 
 #[tokio::test]
-async fn test_fragment_on_interface() {
+async fn fragment_on_union() {
     let query = r#"
         query Query {
             artoo {
@@ -58,38 +64,28 @@ async fn test_fragment_on_interface() {
         }
     "#;
 
-    let (res, errors) = execute(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .await
-    .unwrap();
+    let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
+
+    let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
+        .await
+        .unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
         res,
         graphql_value!({
-            "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"}
+            "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"},
         }),
     );
 
-    let (res, errors) = execute_sync(
-        query,
-        None,
-        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
-        &Variables::new(),
-        &(),
-    )
-    .unwrap();
+    let (res, errors) =
+        juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
 
     assert_eq!(errors.len(), 0);
     assert_eq!(
         res,
         graphql_value!({
-            "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"}
+            "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"},
         }),
     );
 }
diff --git a/juniper/src/executor_tests/directives.rs b/juniper/src/executor_tests/directives.rs
index 791fe2c68..50cc5a0ea 100644
--- a/juniper/src/executor_tests/directives.rs
+++ b/juniper/src/executor_tests/directives.rs
@@ -9,11 +9,11 @@ struct TestType;
 
 #[crate::graphql_object]
 impl TestType {
-    fn a() -> &str {
+    fn a() -> &'static str {
         "a"
     }
 
-    fn b() -> &str {
+    fn b() -> &'static str {
         "b"
     }
 }
diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs
index fb45ba175..3d6f098dd 100644
--- a/juniper/src/executor_tests/executor.rs
+++ b/juniper/src/executor_tests/executor.rs
@@ -1,9 +1,9 @@
 mod field_execution {
     use crate::{
         ast::InputValue,
+        graphql_value,
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
-        value::Value,
     };
 
     struct DataType;
@@ -11,22 +11,22 @@ mod field_execution {
 
     #[crate::graphql_object]
     impl DataType {
-        fn a() -> &str {
+        fn a() -> &'static str {
             "Apple"
         }
-        fn b() -> &str {
+        fn b() -> &'static str {
             "Banana"
         }
-        fn c() -> &str {
+        fn c() -> &'static str {
             "Cookie"
         }
-        fn d() -> &str {
+        fn d() -> &'static str {
             "Donut"
         }
-        fn e() -> &str {
+        fn e() -> &'static str {
             "Egg"
         }
-        fn f() -> &str {
+        fn f() -> &'static str {
             "Fish"
         }
 
@@ -41,13 +41,13 @@ mod field_execution {
 
     #[crate::graphql_object]
     impl DeepDataType {
-        fn a() -> &str {
+        fn a() -> &'static str {
             "Already Been Done"
         }
-        fn b() -> &str {
+        fn b() -> &'static str {
             "Boring"
         }
-        fn c() -> Vec<Option<&str>> {
+        fn c() -> Vec<Option<&'static str>> {
             vec![Some("Contrived"), None, Some("Confusing")]
         }
 
@@ -64,30 +64,31 @@ mod field_execution {
             EmptySubscription::<()>::new(),
         );
         let doc = r"
-          query Example($size: Int) {
-            a,
-            b,
-            x: c
-            ...c
-            f
-            ...on DataType {
-              pic(size: $size)
-            }
-            deep {
-              a
-              b
-              c
-              deeper {
-                a
-                b
-              }
+            query Example($size: Int) {
+                a,
+                b,
+                x: c
+                ...c
+                f
+                ...on DataType {
+                    pic(size: $size)
+                }
+                deep {
+                    a
+                    b
+                    c
+                    deeper {
+                        a
+                        b
+                    }
+                }
             }
-          }
 
-          fragment c on DataType {
-            d
-            e
-          }";
+            fragment c on DataType {
+                d
+                e
+            }
+        ";
 
         let vars = vec![("size".to_owned(), InputValue::scalar(100))]
             .into_iter()
@@ -103,60 +104,31 @@ mod field_execution {
 
         assert_eq!(
             result,
-            Value::object(
-                vec![
-                    ("a", Value::scalar("Apple")),
-                    ("b", Value::scalar("Banana")),
-                    ("x", Value::scalar("Cookie")),
-                    ("d", Value::scalar("Donut")),
-                    ("e", Value::scalar("Egg")),
-                    ("f", Value::scalar("Fish")),
-                    ("pic", Value::scalar("Pic of size: 100")),
-                    (
-                        "deep",
-                        Value::object(
-                            vec![
-                                ("a", Value::scalar("Already Been Done")),
-                                ("b", Value::scalar("Boring")),
-                                (
-                                    "c",
-                                    Value::list(vec![
-                                        Value::scalar("Contrived"),
-                                        Value::null(),
-                                        Value::scalar("Confusing"),
-                                    ]),
-                                ),
-                                (
-                                    "deeper",
-                                    Value::list(vec![
-                                        Value::object(
-                                            vec![
-                                                ("a", Value::scalar("Apple")),
-                                                ("b", Value::scalar("Banana")),
-                                            ]
-                                            .into_iter()
-                                            .collect(),
-                                        ),
-                                        Value::null(),
-                                        Value::object(
-                                            vec![
-                                                ("a", Value::scalar("Apple")),
-                                                ("b", Value::scalar("Banana")),
-                                            ]
-                                            .into_iter()
-                                            .collect(),
-                                        ),
-                                    ]),
-                                ),
-                            ]
-                            .into_iter()
-                            .collect(),
-                        ),
-                    ),
-                ]
-                .into_iter()
-                .collect()
-            )
+            graphql_value!({
+                "a": "Apple",
+                "b": "Banana",
+                "x": "Cookie",
+                "d": "Donut",
+                "e": "Egg",
+                "f": "Fish",
+                "pic": "Pic of size: 100",
+                "deep": {
+                    "a": "Already Been Done",
+                    "b": "Boring",
+                    "c": ["Contrived", None, "Confusing"],
+                    "deeper": [
+                        {
+                            "a": "Apple",
+                            "b": "Banana",
+                        },
+                        None,
+                        {
+                            "a": "Apple",
+                            "b": "Banana",
+                        },
+                    ],
+                },
+            }),
         );
     }
 }
@@ -165,20 +137,19 @@ mod merge_parallel_fragments {
     use crate::{
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
-        value::Value,
     };
 
     struct Type;
 
     #[crate::graphql_object]
     impl Type {
-        fn a() -> &str {
+        fn a() -> &'static str {
             "Apple"
         }
-        fn b() -> &str {
+        fn b() -> &'static str {
             "Banana"
         }
-        fn c() -> &str {
+        fn c() -> &'static str {
             "Cherry"
         }
         fn deep() -> Type {
@@ -194,15 +165,16 @@ mod merge_parallel_fragments {
             EmptySubscription::<()>::new(),
         );
         let doc = r"
-          { a, ...FragOne, ...FragTwo }
-          fragment FragOne on Type {
-            b
-            deep { b, deeper: deep { b } }
-          }
-          fragment FragTwo on Type {
-            c
-            deep { c, deeper: deep { c } }
-          }";
+            { a, ...FragOne, ...FragTwo }
+            fragment FragOne on Type {
+                b
+                deep { b, deeper: deep { b } }
+            }
+            fragment FragTwo on Type {
+                c
+                deep { c, deeper: deep { c } }
+            }
+        ";
 
         let vars = vec![].into_iter().collect();
 
@@ -216,37 +188,19 @@ mod merge_parallel_fragments {
 
         assert_eq!(
             result,
-            Value::object(
-                vec![
-                    ("a", Value::scalar("Apple")),
-                    ("b", Value::scalar("Banana")),
-                    (
-                        "deep",
-                        Value::object(
-                            vec![
-                                ("b", Value::scalar("Banana")),
-                                (
-                                    "deeper",
-                                    Value::object(
-                                        vec![
-                                            ("b", Value::scalar("Banana")),
-                                            ("c", Value::scalar("Cherry")),
-                                        ]
-                                        .into_iter()
-                                        .collect(),
-                                    ),
-                                ),
-                                ("c", Value::scalar("Cherry")),
-                            ]
-                            .into_iter()
-                            .collect(),
-                        ),
-                    ),
-                    ("c", Value::scalar("Cherry")),
-                ]
-                .into_iter()
-                .collect()
-            )
+            graphql_value!({
+                "a": "Apple",
+                "b": "Banana",
+                "deep": {
+                    "b": "Banana",
+                    "deeper": {
+                        "b": "Banana",
+                        "c": "Cherry",
+                    },
+                    "c": "Cherry",
+                },
+                "c": "Cherry",
+            }),
         );
     }
 }
@@ -255,7 +209,6 @@ mod merge_parallel_inline_fragments {
     use crate::{
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
-        value::Value,
     };
 
     struct Type;
@@ -263,13 +216,13 @@ mod merge_parallel_inline_fragments {
 
     #[crate::graphql_object]
     impl Type {
-        fn a() -> &str {
+        fn a() -> &'static str {
             "Apple"
         }
-        fn b() -> &str {
+        fn b() -> &'static str {
             "Banana"
         }
-        fn c() -> &str {
+        fn c() -> &'static str {
             "Cherry"
         }
         fn deep() -> Type {
@@ -282,13 +235,13 @@ mod merge_parallel_inline_fragments {
 
     #[crate::graphql_object]
     impl Other {
-        fn a() -> &str {
+        fn a() -> &'static str {
             "Apple"
         }
-        fn b() -> &str {
+        fn b() -> &'static str {
             "Banana"
         }
-        fn c() -> &str {
+        fn c() -> &'static str {
             "Cherry"
         }
         fn deep() -> Type {
@@ -307,29 +260,29 @@ mod merge_parallel_inline_fragments {
             EmptySubscription::<()>::new(),
         );
         let doc = r"
-          { a, ...FragOne }
-          fragment FragOne on Type {
-            b
-            deep: deep {
+            { a, ...FragOne }
+            fragment FragOne on Type {
                 b
-                deeper: other {
-                    deepest: deep {
-                        b
-                    }
-                }
-
-                ... on Type {
-                    c
+                deep: deep {
+                    b
                     deeper: other {
                         deepest: deep {
-                            c
+                            b
+                        }
+                    }
+
+                    ... on Type {
+                        c
+                        deeper: other {
+                            deepest: deep {
+                                c
+                            }
                         }
                     }
                 }
+                c
             }
-
-            c
-          }";
+        ";
 
         let vars = vec![].into_iter().collect();
 
@@ -343,61 +296,26 @@ mod merge_parallel_inline_fragments {
 
         assert_eq!(
             result,
-            Value::object(
-                vec![
-                    ("a", Value::scalar("Apple")),
-                    ("b", Value::scalar("Banana")),
-                    (
-                        "deep",
-                        Value::object(
-                            vec![
-                                ("b", Value::scalar("Banana")),
-                                (
-                                    "deeper",
-                                    Value::list(vec![
-                                        Value::object(
-                                            vec![(
-                                                "deepest",
-                                                Value::object(
-                                                    vec![
-                                                        ("b", Value::scalar("Banana")),
-                                                        ("c", Value::scalar("Cherry")),
-                                                    ]
-                                                    .into_iter()
-                                                    .collect(),
-                                                ),
-                                            )]
-                                            .into_iter()
-                                            .collect(),
-                                        ),
-                                        Value::object(
-                                            vec![(
-                                                "deepest",
-                                                Value::object(
-                                                    vec![
-                                                        ("b", Value::scalar("Banana")),
-                                                        ("c", Value::scalar("Cherry")),
-                                                    ]
-                                                    .into_iter()
-                                                    .collect(),
-                                                ),
-                                            )]
-                                            .into_iter()
-                                            .collect(),
-                                        ),
-                                    ]),
-                                ),
-                                ("c", Value::scalar("Cherry")),
-                            ]
-                            .into_iter()
-                            .collect(),
-                        ),
-                    ),
-                    ("c", Value::scalar("Cherry")),
-                ]
-                .into_iter()
-                .collect()
-            )
+            graphql_value!({
+                "a": "Apple",
+                "b": "Banana",
+                "deep": {
+                    "b": "Banana",
+                    "deeper": [{
+                        "deepest": {
+                            "b": "Banana",
+                            "c": "Cherry",
+                        },
+                    }, {
+                        "deepest": {
+                            "b": "Banana",
+                            "c": "Cherry",
+                        },
+                    }],
+                    "c": "Cherry",
+                },
+                "c": "Cherry",
+            }),
         );
     }
 }
@@ -405,9 +323,9 @@ mod merge_parallel_inline_fragments {
 mod threads_context_correctly {
     use crate::{
         executor::Context,
+        graphql_value,
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
-        value::Value,
     };
 
     struct Schema;
@@ -454,14 +372,7 @@ mod threads_context_correctly {
 
         println!("Result: {:#?}", result);
 
-        assert_eq!(
-            result,
-            Value::object(
-                vec![("a", Value::scalar("Context value"))]
-                    .into_iter()
-                    .collect()
-            )
-        );
+        assert_eq!(result, graphql_value!({"a": "Context value"}));
     }
 }
 
@@ -475,6 +386,7 @@ mod dynamic_context_switching {
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
         value::Value,
+        Executor, ScalarValue,
     };
 
     struct Schema;
@@ -494,7 +406,11 @@ mod dynamic_context_switching {
 
     #[graphql_object(context = OuterContext)]
     impl Schema {
-        fn item_opt(_context: &OuterContext, key: i32) -> Option<(&InnerContext, ItemRef)> {
+        fn item_opt<'e, S: ScalarValue>(
+            executor: &'e Executor<'_, '_, OuterContext, S>,
+            _context: &OuterContext,
+            key: i32,
+        ) -> Option<(&'e InnerContext, ItemRef)> {
             executor.context().items.get(&key).map(|c| (c, ItemRef))
         }
 
@@ -653,11 +569,9 @@ mod dynamic_context_switching {
             EmptyMutation::<OuterContext>::new(),
             EmptySubscription::<OuterContext>::new(),
         );
-        let doc = r"
-          {
+        let doc = r"{
             missing: itemRes(key: 2) { value }
-          }
-          ";
+        }";
 
         let vars = vec![].into_iter().collect();
 
@@ -687,7 +601,7 @@ mod dynamic_context_switching {
         assert_eq!(
             errs,
             vec![ExecutionError::new(
-                SourcePosition::new(25, 2, 12),
+                SourcePosition::new(14, 1, 12),
                 &["missing"],
                 FieldError::new("Could not find key 2", Value::null()),
             )]
@@ -880,13 +794,13 @@ mod propagates_errors_to_nullable_fields {
         fn non_nullable_field() -> Inner {
             Inner
         }
-        fn nullable_error_field() -> FieldResult<Option<&str>> {
+        fn nullable_error_field() -> FieldResult<Option<&'static str>> {
             Err("Error for nullableErrorField")?
         }
-        fn non_nullable_error_field() -> FieldResult<&str> {
+        fn non_nullable_error_field() -> FieldResult<&'static str> {
             Err("Error for nonNullableErrorField")?
         }
-        fn custom_error_field() -> Result<&str, CustomError> {
+        fn custom_error_field() -> Result<&'static str, CustomError> {
             Err(CustomError::NotFound)
         }
     }
@@ -1160,18 +1074,18 @@ mod propagates_errors_to_nullable_fields {
 
 mod named_operations {
     use crate::{
+        graphql_object, graphql_value,
         schema::model::RootNode,
         types::scalars::{EmptyMutation, EmptySubscription},
-        value::Value,
         GraphQLError,
     };
 
     struct Schema;
 
-    #[crate::graphql_object]
+    #[graphql_object]
     impl Schema {
-        fn a(p: Option<String>) -> &str {
-            let _ = p;
+        fn a(p: Option<String>) -> &'static str {
+            drop(p);
             "b"
         }
     }
@@ -1187,16 +1101,12 @@ mod named_operations {
 
         let vars = vec![].into_iter().collect();
 
-        let (result, errs) = crate::execute(doc, None, &schema, &vars, &())
+        let (res, errs) = crate::execute(doc, None, &schema, &vars, &())
             .await
             .expect("Execution failed");
 
         assert_eq!(errs, []);
-
-        assert_eq!(
-            result,
-            Value::object(vec![("a", Value::scalar("b"))].into_iter().collect())
-        );
+        assert_eq!(res, graphql_value!({"a": "b"}));
     }
 
     #[tokio::test]
@@ -1210,16 +1120,12 @@ mod named_operations {
 
         let vars = vec![].into_iter().collect();
 
-        let (result, errs) = crate::execute(doc, None, &schema, &vars, &())
+        let (res, errs) = crate::execute(doc, None, &schema, &vars, &())
             .await
             .expect("Execution failed");
 
         assert_eq!(errs, []);
-
-        assert_eq!(
-            result,
-            Value::object(vec![("a", Value::scalar("b"))].into_iter().collect())
-        );
+        assert_eq!(res, graphql_value!({"a": "b"}));
     }
 
     #[tokio::test]
@@ -1234,16 +1140,12 @@ mod named_operations {
 
         let vars = vec![].into_iter().collect();
 
-        let (result, errs) = crate::execute(doc, Some("OtherExample"), &schema, &vars, &())
+        let (res, errs) = crate::execute(doc, Some("OtherExample"), &schema, &vars, &())
             .await
             .expect("Execution failed");
 
         assert_eq!(errs, []);
-
-        assert_eq!(
-            result,
-            Value::object(vec![("second", Value::scalar("b"))].into_iter().collect())
-        );
+        assert_eq!(res, graphql_value!({"second": "b"}));
     }
 
     #[tokio::test]
diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs
index 8e86553e8..d9c48bda5 100644
--- a/juniper/src/executor_tests/introspection/input_object.rs
+++ b/juniper/src/executor_tests/introspection/input_object.rs
@@ -3,6 +3,7 @@
 use crate::{
     ast::{FromInputValue, InputValue},
     executor::Variables,
+    graphql_object, graphql_value,
     schema::model::RootNode,
     types::scalars::{EmptyMutation, EmptySubscription},
     value::{DefaultScalarValue, Object, Value},
@@ -82,7 +83,7 @@ struct FieldWithDefaults {
     field_two: i32,
 }
 
-#[crate::graphql_object]
+#[graphql_object]
 impl Root {
     fn test_field(
         a1: DefaultName,
@@ -149,8 +150,7 @@ where
 
 #[tokio::test]
 async fn default_name_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "DefaultName") {
             name
             description
@@ -165,70 +165,35 @@ async fn default_name_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("DefaultName"))
+            Some(&graphql_value!("DefaultName")),
         );
         assert_eq!(
             type_info.get_field_value("description"),
-            Some(&Value::null())
+            Some(&graphql_value!(None)),
         );
 
         assert_eq!(fields.len(), 2);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldTwo")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldTwo",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
     })
     .await;
 }
@@ -256,8 +221,7 @@ fn default_name_input_value() {
 
 #[tokio::test]
 async fn no_trailing_comma_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "NoTrailingComma") {
             name
             description
@@ -272,78 +236,42 @@ async fn no_trailing_comma_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("NoTrailingComma"))
+            Some(&graphql_value!("NoTrailingComma")),
         );
         assert_eq!(
             type_info.get_field_value("description"),
-            Some(&Value::null())
+            Some(&graphql_value!(None)),
         );
 
         assert_eq!(fields.len(), 2);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldTwo")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldTwo",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
     })
     .await;
 }
 
 #[tokio::test]
 async fn derive_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "Derive") {
             name
             description
@@ -358,45 +286,27 @@ async fn derive_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("Derive"))
+            Some(&graphql_value!("Derive")),
         );
         assert_eq!(
             type_info.get_field_value("description"),
-            Some(&Value::null())
+            Some(&graphql_value!(None)),
         );
 
         assert_eq!(fields.len(), 1);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
     })
     .await;
 }
@@ -408,7 +318,7 @@ fn derive_derived() {
             "{:?}",
             Derive {
                 field_one: "test".to_owned(),
-            }
+            },
         ),
         "Derive { field_one: \"test\" }"
     );
@@ -416,8 +326,7 @@ fn derive_derived() {
 
 #[tokio::test]
 async fn named_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "ANamedInputObject") {
             name
             description
@@ -432,53 +341,34 @@ async fn named_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("ANamedInputObject"))
+            Some(&graphql_value!("ANamedInputObject"))
         );
         assert_eq!(
             type_info.get_field_value("description"),
-            Some(&Value::null())
+            Some(&graphql_value!(None))
         );
 
         assert_eq!(fields.len(), 1);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
     })
     .await;
 }
 
 #[tokio::test]
 async fn description_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "Description") {
             name
             description
@@ -493,53 +383,34 @@ async fn description_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("Description"))
+            Some(&graphql_value!("Description")),
         );
         assert_eq!(
             type_info.get_field_value("description"),
-            Some(&Value::scalar("Description for the input object"))
+            Some(&graphql_value!("Description for the input object")),
         );
 
         assert_eq!(fields.len(), 1);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                ("description", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "description": None,
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
     })
     .await;
 }
 
 #[tokio::test]
 async fn field_description_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "FieldDescription") {
             name
             description
@@ -554,78 +425,42 @@ async fn field_description_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("FieldDescription"))
+            Some(&graphql_value!("FieldDescription")),
         );
         assert_eq!(
             type_info.get_field_value("description"),
-            Some(&Value::null())
+            Some(&graphql_value!(None)),
         );
 
         assert_eq!(fields.len(), 2);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                ("description", Value::scalar("The first field")),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldTwo")),
-                ("description", Value::scalar("The second field")),
-                (
-                    "type",
-                    Value::object(
-                        vec![(
-                            "ofType",
-                            Value::object(
-                                vec![("name", Value::scalar("String"))]
-                                    .into_iter()
-                                    .collect(),
-                            ),
-                        )]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-                ("defaultValue", Value::null()),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "description": "The first field",
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldTwo",
+            "description": "The second field",
+            "type": {
+                "ofType": {"name": "String"},
+            },
+            "defaultValue": None,
+        })));
     })
     .await;
 }
 
 #[tokio::test]
 async fn field_with_defaults_introspection() {
-    let doc = r#"
-    {
+    let doc = r#"{
         __type(name: "FieldWithDefaults") {
             name
             inputFields {
@@ -636,42 +471,25 @@ async fn field_with_defaults_introspection() {
                 defaultValue
             }
         }
-    }
-    "#;
+    }"#;
 
     run_type_info_query(doc, |type_info, fields| {
         assert_eq!(
             type_info.get_field_value("name"),
-            Some(&Value::scalar("FieldWithDefaults"))
+            Some(&graphql_value!("FieldWithDefaults")),
         );
 
         assert_eq!(fields.len(), 2);
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldOne")),
-                (
-                    "type",
-                    Value::object(vec![("name", Value::scalar("Int"))].into_iter().collect()),
-                ),
-                ("defaultValue", Value::scalar("123")),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(fields.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("fieldTwo")),
-                (
-                    "type",
-                    Value::object(vec![("name", Value::scalar("Int"))].into_iter().collect()),
-                ),
-                ("defaultValue", Value::scalar("456")),
-            ]
-            .into_iter()
-            .collect(),
-        )));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldOne",
+            "type": {"name": "Int"},
+            "defaultValue": "123",
+        })));
+        assert!(fields.contains(&graphql_value!({
+            "name": "fieldTwo",
+            "type": {"name": "Int"},
+            "defaultValue": "456",
+        })));
     })
     .await;
 }
diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs
index 74403ff85..8bf6a14e6 100644
--- a/juniper/src/executor_tests/introspection/mod.rs
+++ b/juniper/src/executor_tests/introspection/mod.rs
@@ -56,13 +56,11 @@ impl Root {
         Sample::One
     }
 
-    #[graphql(arguments(
-        first(description = "The first number",),
-        second(description = "The second number", default = 123),
-    ))]
-
     /// A sample scalar field on the object
-    fn sample_scalar(first: i32, second: i32) -> Scalar {
+    fn sample_scalar(
+        #[graphql(description = "The first number")] first: i32,
+        #[graphql(description = "The second number", default = 123)] second: i32,
+    ) -> Scalar {
         Scalar(first + second)
     }
 }
diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs
index 76f73e014..1cefcb83f 100644
--- a/juniper/src/executor_tests/variables.rs
+++ b/juniper/src/executor_tests/variables.rs
@@ -77,14 +77,9 @@ impl TestType {
         format!("{:?}", input)
     }
 
-    #[graphql(
-        arguments(
-            input(
-                default = "Hello World".to_string(),
-            )
-        )
-    )]
-    fn field_with_default_argument_value(input: String) -> String {
+    fn field_with_default_argument_value(
+        #[graphql(default = "Hello World")] input: String,
+    ) -> String {
         format!("{:?}", input)
     }
 
diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs
deleted file mode 100644
index 9f5d038d8..000000000
--- a/juniper/src/macros/tests/args.rs
+++ /dev/null
@@ -1,1164 +0,0 @@
-#![allow(unused)]
-
-use crate::{
-    executor::Variables,
-    schema::model::RootNode,
-    types::scalars::{EmptyMutation, EmptySubscription},
-    value::{DefaultScalarValue, Value},
-    GraphQLInputObject,
-};
-
-struct Root;
-
-/*
-
-Syntax to validate:
-
-* No args at all
-* Executor arg vs. no executor arg
-* Single arg vs. multi arg
-* Trailing comma vs. no trailing comma
-* Default value vs. no default value
-* Complex default value
-* Description vs. no description
-
-*/
-
-#[derive(GraphQLInputObject, Debug)]
-struct Point {
-    x: i32,
-}
-
-#[crate::graphql_object]
-impl Root {
-    fn simple() -> i32 {
-        0
-    }
-    fn exec_arg(_executor: &Executor) -> i32 {
-        0
-    }
-    fn exec_arg_and_more(_executor: &Executor, arg: i32) -> i32 {
-        arg
-    }
-
-    fn single_arg(arg: i32) -> i32 {
-        arg
-    }
-
-    fn multi_args(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    fn multi_args_trailing_comma(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(arg(description = "The arg")))]
-    fn single_arg_descr(arg: i32) -> i32 {
-        arg
-    }
-
-    #[graphql(arguments(r#arg(description = "The arg")))]
-    fn single_arg_descr_raw_idents(arg: i32) -> i32 {
-        arg
-    }
-
-    #[graphql(arguments(
-        arg1(description = "The first arg",),
-        arg2(description = "The second arg")
-    ))]
-    fn multi_args_descr(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(
-        r#arg1(description = "The first arg",),
-        r#arg2(description = "The second arg")
-    ))]
-    fn multi_args_descr_raw_idents(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(
-        arg1(description = "The first arg",),
-        arg2(description = "The second arg",)
-    ))]
-    fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    fn attr_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 {
-        0
-    }
-
-    fn attr_arg_descr_collapse(
-        #[graphql(description = "The first arg")]
-        #[graphql(description = "and more details")]
-        arg: i32,
-    ) -> i32 {
-        0
-    }
-
-    #[graphql(arguments(arg(default = 123,),))]
-    fn arg_with_default(arg: i32) -> i32 {
-        arg
-    }
-
-    #[graphql(arguments(arg1(default = 123,), arg2(default = 456,)))]
-    fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(arg1(default = 123,), arg2(default = 456,),))]
-    fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(arg(default = 123, description = "The arg")))]
-    fn arg_with_default_descr(arg: i32) -> i32 {
-        arg
-    }
-
-    #[graphql(arguments(r#arg(default = 123, description = "The arg")))]
-    fn arg_with_default_descr_raw_ident(arg: i32) -> i32 {
-        arg
-    }
-
-    #[graphql(arguments(
-        arg1(default = 123, description = "The first arg"),
-        arg2(default = 456, description = "The second arg")
-    ))]
-    fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(
-        r#arg1(default = 123, description = "The first arg"),
-        r#arg2(default = 456, description = "The second arg")
-    ))]
-    fn multi_args_with_default_descr_raw_ident(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(
-        arg1(default = 123, description = "The first arg",),
-        arg2(default = 456, description = "The second arg",)
-    ))]
-    fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(arguments(
-        r#arg1(default = 123, description = "The first arg",),
-        r#arg2(default = 456, description = "The second arg",)
-    ))]
-    fn multi_args_with_default_trailing_comma_descr_raw_ident(arg1: i32, arg2: i32) -> i32 {
-        arg1 + arg2
-    }
-
-    #[graphql(
-        arguments(
-            arg1(
-                default = "test".to_string(),
-                description = "A string default argument",
-            ),
-            arg2(
-                default = Point{ x: 1 },
-                description = "An input object default argument",
-            )
-        ),
-    )]
-    fn args_with_complex_default(arg1: String, arg2: Point) -> i32 {
-        let _ = arg1;
-        let _ = arg2;
-        0
-    }
-}
-
-async fn run_args_info_query<F>(field_name: &str, f: F)
-where
-    F: Fn(&Vec<Value<DefaultScalarValue>>) -> (),
-{
-    let doc = r#"
-    {
-        __type(name: "Root") {
-            fields {
-                name
-                args {
-                    name
-                    description
-                    defaultValue
-                    type {
-                        name
-                        ofType {
-                            name
-                        }
-                    }
-                }
-            }
-        }
-    }
-    "#;
-    let schema = RootNode::new(
-        Root {},
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-
-    let (result, errs) = crate::execute(doc, None, &schema, &Variables::new(), &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let fields = type_info
-        .get_field_value("fields")
-        .expect("fields field missing")
-        .as_list_value()
-        .expect("fields not a list");
-
-    let field = fields
-        .into_iter()
-        .filter(|f| {
-            f.as_object_value()
-                .expect("Field not an object")
-                .get_field_value("name")
-                .expect("name field missing from field")
-                .as_scalar_value::<String>()
-                .expect("name is not a string")
-                == field_name
-        })
-        .next()
-        .expect("Field not found")
-        .as_object_value()
-        .expect("Field is not an object");
-
-    println!("Field: {:#?}", field);
-
-    let args = field
-        .get_field_value("args")
-        .expect("args missing from field")
-        .as_list_value()
-        .expect("args is not a list");
-
-    println!("Args: {:#?}", args);
-
-    f(args);
-}
-
-#[tokio::test]
-async fn introspect_field_simple() {
-    run_args_info_query("simple", |args| {
-        assert_eq!(args.len(), 0);
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_exec_arg() {
-    run_args_info_query("execArg", |args| {
-        assert_eq!(args.len(), 0);
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_exec_arg_and_more() {
-    run_args_info_query("execArgAndMore", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::null()),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_single_arg() {
-    run_args_info_query("singleArg", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::null()),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args() {
-    run_args_info_query("multiArgs", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::null()),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::null()),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_trailing_comma() {
-    run_args_info_query("multiArgsTrailingComma", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::null()),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::null()),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_single_arg_descr() {
-    run_args_info_query("singleArgDescr", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::scalar("The arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_single_arg_descr_raw_idents() {
-    run_args_info_query("singleArgDescrRawIdents", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::scalar("The arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_descr() {
-    run_args_info_query("multiArgsDescr", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_descr_raw_idents() {
-    run_args_info_query("multiArgsDescrRawIdents", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_descr_trailing_comma() {
-    run_args_info_query("multiArgsDescrTrailingComma", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_attr_arg_descr() {
-    run_args_info_query("attrArgDescr", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::scalar("The arg")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    });
-}
-
-#[tokio::test]
-async fn introspect_field_attr_arg_descr_collapse() {
-    run_args_info_query("attrArgDescrCollapse", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::scalar("The arg\nand more details")),
-                ("defaultValue", Value::null()),
-                (
-                    "type",
-                    Value::object(
-                        vec![
-                            ("name", Value::null()),
-                            (
-                                "ofType",
-                                Value::object(
-                                    vec![("name", Value::scalar("Int"))].into_iter().collect(),
-                                ),
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    });
-}
-
-#[tokio::test]
-async fn introspect_field_arg_with_default() {
-    run_args_info_query("argWithDefault", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::null()),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_with_default() {
-    run_args_info_query("multiArgsWithDefault", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::null()),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::null()),
-                ("defaultValue", Value::scalar("456")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_with_default_trailing_comma() {
-    run_args_info_query("multiArgsWithDefaultTrailingComma", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::null()),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::null()),
-                ("defaultValue", Value::scalar("456")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_arg_with_default_descr() {
-    run_args_info_query("argWithDefaultDescr", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::scalar("The arg")),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_arg_with_default_descr_raw_ident() {
-    run_args_info_query("argWithDefaultDescrRawIdent", |args| {
-        assert_eq!(args.len(), 1);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg")),
-                ("description", Value::scalar("The arg")),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_with_default_descr() {
-    run_args_info_query("multiArgsWithDefaultDescr", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::scalar("456")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_with_default_descr_raw_ident() {
-    run_args_info_query("multiArgsWithDefaultDescrRawIdent", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::scalar("456")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_with_default_trailing_comma_descr() {
-    run_args_info_query("multiArgsWithDefaultTrailingCommaDescr", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::scalar("456")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_multi_args_with_default_trailing_comma_descr_raw_ident() {
-    run_args_info_query("multiArgsWithDefaultTrailingCommaDescrRawIdent", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("The first arg")),
-                ("defaultValue", Value::scalar("123")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                ("description", Value::scalar("The second arg")),
-                ("defaultValue", Value::scalar("456")),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Int")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_field_args_with_complex_default() {
-    run_args_info_query("argsWithComplexDefault", |args| {
-        assert_eq!(args.len(), 2);
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg1")),
-                ("description", Value::scalar("A string default argument")),
-                ("defaultValue", Value::scalar(r#""test""#)),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("String")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-
-        assert!(args.contains(&Value::object(
-            vec![
-                ("name", Value::scalar("arg2")),
-                (
-                    "description",
-                    Value::scalar("An input object default argument"),
-                ),
-                ("defaultValue", Value::scalar(r#"{x: 1}"#)),
-                (
-                    "type",
-                    Value::object(
-                        vec![("name", Value::scalar("Point")), ("ofType", Value::null())]
-                            .into_iter()
-                            .collect(),
-                    ),
-                ),
-            ]
-            .into_iter()
-            .collect(),
-        )));
-    })
-    .await;
-}
diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs
deleted file mode 100644
index e1a73039f..000000000
--- a/juniper/src/macros/tests/field.rs
+++ /dev/null
@@ -1,731 +0,0 @@
-/*
-
-Syntax to validate:
-
-* Object vs. interface
-* Description vs. no description
-* Deprecated vs. not deprecated
-* FieldResult vs. object directly
-* Return vs. implicit return
-
-*/
-
-#![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(impl = InterfaceValue)]
-impl Root {
-    fn simple() -> i32 {
-        0
-    }
-
-    /// Field description
-    fn description() -> i32 {
-        0
-    }
-
-    #[deprecated]
-    fn deprecated_outer() -> bool {
-        true
-    }
-
-    #[deprecated(note = "Deprecation Reason")]
-    fn deprecated_outer_with_reason() -> bool {
-        true
-    }
-
-    #[graphql(deprecated = "Deprecation reason")]
-    fn deprecated() -> i32 {
-        0
-    }
-
-    #[graphql(deprecated = "Deprecation reason", description = "Field description")]
-    fn deprecated_descr() -> i32 {
-        0
-    }
-
-    /// Field description
-    fn attr_description() -> i32 {
-        0
-    }
-
-    /// Field description
-    /// with `collapse_docs` behavior
-    fn attr_description_collapse() -> i32 {
-        0
-    }
-
-    /// Get the i32 representation of 0.
-    ///
-    /// - This comment is longer.
-    /// - These two lines are rendered as bullets by GraphiQL.
-    ///     - subsection
-    fn attr_description_long() -> i32 {
-        0
-    }
-
-    #[graphql(deprecated)]
-    fn attr_deprecated() -> i32 {
-        0
-    }
-
-    #[graphql(deprecated = "Deprecation reason")]
-    fn attr_deprecated_reason() -> i32 {
-        0
-    }
-
-    /// Field description
-    #[graphql(deprecated = "Deprecation reason")]
-    fn attr_deprecated_descr() -> i32 {
-        0
-    }
-
-    fn with_field_result() -> FieldResult<i32> {
-        Ok(0)
-    }
-
-    fn with_return() -> i32 {
-        0
-    }
-
-    fn with_return_field_result() -> FieldResult<i32> {
-        Ok(0)
-    }
-}
-
-#[graphql_interface]
-impl Interface for Root {
-    fn simple(&self) -> i32 {
-        0
-    }
-
-    fn description(&self) -> i32 {
-        0
-    }
-
-    fn deprecated(&self) -> i32 {
-        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)]
-trait Interface {
-    fn simple(&self) -> i32;
-
-    #[graphql(desc = "Field description")]
-    fn description(&self) -> i32;
-
-    #[graphql(deprecated = "Deprecation reason")]
-    fn deprecated(&self) -> i32;
-
-    #[graphql(desc = "Field description", deprecated = "Deprecation reason")]
-    fn deprecated_descr(&self) -> i32;
-
-    /// Field description
-    fn attr_description(&self) -> i32;
-
-    /// Field description
-    /// with `collapse_docs` behavior
-    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.
-    fn attr_description_long(&self) -> i32;
-
-    #[deprecated]
-    fn attr_deprecated(&self) -> i32;
-
-    #[deprecated(note = "Deprecation reason")]
-    fn attr_deprecated_reason(&self) -> i32;
-
-    /// Field description
-    #[deprecated(note = "Deprecation reason")]
-    fn attr_deprecated_descr(&self) -> i32;
-}
-
-async fn run_field_info_query<F>(type_name: &str, field_name: &str, f: F)
-where
-    F: Fn(&Object<DefaultScalarValue>) -> (),
-{
-    let doc = r#"
-    query ($typeName: String!) {
-        __type(name: $typeName) {
-            fields(includeDeprecated: true) {
-                name
-                description
-                isDeprecated
-                deprecationReason
-            }
-        }
-    }
-    "#;
-    let schema = RootNode::new(
-        Root {},
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-    let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))]
-        .into_iter()
-        .collect();
-
-    let (result, errs) = crate::execute(doc, None, &schema, &vars, &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let fields = type_info
-        .get_field_value("fields")
-        .expect("fields field missing")
-        .as_list_value()
-        .expect("fields not a list");
-
-    let field = fields
-        .into_iter()
-        .filter(|f| {
-            f.as_object_value()
-                .expect("Field not an object")
-                .get_field_value("name")
-                .expect("name field missing from field")
-                .as_scalar_value::<String>()
-                .expect("name is not a string")
-                == field_name
-        })
-        .next()
-        .expect("Field not found")
-        .as_object_value()
-        .expect("Field is not an object");
-
-    println!("Field: {:#?}", field);
-
-    f(field);
-}
-
-#[tokio::test]
-async fn introspect_object_field_simple() {
-    run_field_info_query("Root", "simple", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("simple"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_simple() {
-    run_field_info_query("Interface", "simple", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("simple"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_description() {
-    run_field_info_query("Root", "description", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("description"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_description() {
-    run_field_info_query("Interface", "description", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("description"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_deprecated_outer() {
-    run_field_info_query("Root", "deprecatedOuter", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("deprecatedOuter"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null()),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_deprecated_outer_with_reason() {
-    run_field_info_query("Root", "deprecatedOuterWithReason", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("deprecatedOuterWithReason"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation Reason")),
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_deprecated() {
-    run_field_info_query("Root", "deprecated", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("deprecated"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_deprecated() {
-    run_field_info_query("Interface", "deprecated", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("deprecated"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_deprecated_descr() {
-    run_field_info_query("Root", "deprecatedDescr", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("deprecatedDescr"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_deprecated_descr() {
-    run_field_info_query("Interface", "deprecatedDescr", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("deprecatedDescr"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_attr_description() {
-    run_field_info_query("Root", "attrDescription", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDescription"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_attr_description() {
-    run_field_info_query("Interface", "attrDescription", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDescription"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_attr_description_long() {
-    run_field_info_query("Root", "attrDescriptionLong", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDescriptionLong"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Get the i32 representation of 0.\n\n- This comment is longer.\n- These two lines are rendered as bullets by GraphiQL.\n    - subsection"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    }).await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_attr_description_long() {
-    run_field_info_query("Interface", "attrDescriptionLong", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDescriptionLong"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Get the i32 representation of 0.\n\n- This comment is longer.\n- These two lines are rendered as bullets by GraphiQL."))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    }).await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_attr_description_collapse() {
-    run_field_info_query("Root", "attrDescriptionCollapse", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDescriptionCollapse"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar(
-                "Field description\nwith `collapse_docs` behavior"
-            ))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_attr_description_collapse() {
-    run_field_info_query("Interface", "attrDescriptionCollapse", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDescriptionCollapse"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar(
-                "Field description\nwith `collapse_docs` behavior"
-            ))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(false))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_attr_deprecated() {
-    run_field_info_query("Root", "attrDeprecated", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDeprecated"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_attr_deprecated() {
-    run_field_info_query("Interface", "attrDeprecated", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDeprecated"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::null())
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_attr_deprecated_reason() {
-    run_field_info_query("Root", "attrDeprecatedReason", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDeprecatedReason"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_attr_deprecated_reason() {
-    run_field_info_query("Interface", "attrDeprecatedReason", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDeprecatedReason"))
-        );
-        assert_eq!(field.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_object_field_attr_deprecated_descr() {
-    run_field_info_query("Root", "attrDeprecatedDescr", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDeprecatedDescr"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_interface_field_attr_deprecated_descr() {
-    run_field_info_query("Interface", "attrDeprecatedDescr", |field| {
-        assert_eq!(
-            field.get_field_value("name"),
-            Some(&Value::scalar("attrDeprecatedDescr"))
-        );
-        assert_eq!(
-            field.get_field_value("description"),
-            Some(&Value::scalar("Field description"))
-        );
-        assert_eq!(
-            field.get_field_value("isDeprecated"),
-            Some(&Value::scalar(true))
-        );
-        assert_eq!(
-            field.get_field_value("deprecationReason"),
-            Some(&Value::scalar("Deprecation reason"))
-        );
-    })
-    .await;
-}
diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs
deleted file mode 100644
index d82607da7..000000000
--- a/juniper/src/macros/tests/impl_object.rs
+++ /dev/null
@@ -1,308 +0,0 @@
-use super::util;
-use crate::{graphql_value, EmptyMutation, EmptySubscription, RootNode};
-
-#[derive(Default)]
-struct Context {
-    flag1: bool,
-}
-
-impl crate::Context for Context {}
-
-struct WithLifetime<'a> {
-    value: &'a str,
-}
-
-#[crate::graphql_object(Context=Context)]
-impl<'a> WithLifetime<'a> {
-    fn value(&'a self) -> &'a str {
-        self.value
-    }
-}
-
-struct WithContext;
-
-#[crate::graphql_object(Context=Context)]
-impl WithContext {
-    fn ctx(ctx: &Context) -> bool {
-        ctx.flag1
-    }
-}
-
-#[derive(Default)]
-struct Query {
-    b: bool,
-}
-
-#[crate::graphql_object(
-    scalar = crate::DefaultScalarValue,
-    name = "Query", 
-    context = Context,
-    // FIXME: make async work
-    noasync
-)]
-/// Query Description.
-impl<'a> Query {
-    #[graphql(description = "With Self Description")]
-    fn with_self(&self) -> bool {
-        self.b
-    }
-
-    fn independent() -> i32 {
-        100
-    }
-
-    fn with_executor(_exec: &Executor<Context>) -> bool {
-        true
-    }
-
-    fn with_executor_and_self(&self, _exec: &Executor<Context>) -> bool {
-        true
-    }
-
-    fn with_context(_context: &Context) -> bool {
-        true
-    }
-
-    fn with_context_and_self(&self, _context: &Context) -> bool {
-        true
-    }
-
-    #[graphql(name = "renamed")]
-    fn has_custom_name() -> bool {
-        true
-    }
-
-    #[graphql(description = "attr")]
-    fn has_description_attr() -> bool {
-        true
-    }
-
-    /// Doc description
-    fn has_description_doc_comment() -> bool {
-        true
-    }
-
-    fn has_argument(arg1: bool) -> bool {
-        arg1
-    }
-
-    #[graphql(arguments(default_arg(default = true)))]
-    fn default_argument(default_arg: bool) -> bool {
-        default_arg
-    }
-
-    #[graphql(arguments(arg(description = "my argument description")))]
-    fn arg_with_description(arg: bool) -> bool {
-        arg
-    }
-
-    fn with_context_child(&self) -> WithContext {
-        WithContext
-    }
-
-    fn with_lifetime_child(&self) -> WithLifetime<'a> {
-        WithLifetime { value: "blub" }
-    }
-
-    fn with_mut_arg(mut arg: bool) -> bool {
-        if arg {
-            arg = !arg;
-        }
-        arg
-    }
-}
-
-#[derive(Default)]
-struct Mutation;
-
-#[crate::graphql_object(context = Context)]
-impl Mutation {
-    fn empty() -> bool {
-        true
-    }
-}
-
-#[derive(Default)]
-struct Subscription;
-
-#[crate::graphql_object(context = Context)]
-impl Subscription {
-    fn empty() -> bool {
-        true
-    }
-}
-
-#[tokio::test]
-async fn object_introspect() {
-    let res = util::run_info_query::<Query, Mutation, Subscription>("Query").await;
-    assert_eq!(
-        res,
-        crate::graphql_value!({
-            "name": "Query",
-            "description": "Query Description.",
-            "fields": [
-                {
-                    "name": "withSelf",
-                    "description": "With Self Description",
-                    "args": [],
-                },
-                {
-                    "name": "independent",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withExecutor",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withExecutorAndSelf",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withContext",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withContextAndSelf",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "renamed",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "hasDescriptionAttr",
-                    "description": "attr",
-                    "args": [],
-                },
-                {
-                    "name": "hasDescriptionDocComment",
-                    "description": "Doc description",
-                    "args": [],
-                },
-                {
-                    "name": "hasArgument",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "arg1",
-                            "description": None,
-                            "type": {
-                                "name": None,
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "defaultArgument",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "defaultArg",
-                            "description": None,
-                            "type": {
-                                "name": "Boolean",
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "argWithDescription",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "arg",
-                            "description": "my argument description",
-                            "type": {
-                                "name": None
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "withContextChild",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withLifetimeChild",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withMutArg",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "arg",
-                            "description": None,
-                            "type": {
-                                "name": None,
-                            },
-                        }
-                    ],
-                },
-            ]
-        })
-    );
-}
-
-#[tokio::test]
-async fn object_query() {
-    let doc = r#"
-    query {
-        withSelf
-        independent
-        withExecutor
-        withExecutorAndSelf
-        withContext
-        withContextAndSelf
-        renamed
-        hasArgument(arg1: true)
-        defaultArgument
-        argWithDescription(arg: true)
-        withContextChild {
-            ctx
-        }
-        withLifetimeChild {
-            value
-        }
-        withMutArg(arg: true)
-    }
-    "#;
-    let schema = RootNode::new(
-        Query { b: true },
-        EmptyMutation::<Context>::new(),
-        EmptySubscription::<Context>::new(),
-    );
-    let vars = std::collections::HashMap::new();
-
-    let (result, errs) = crate::execute(doc, None, &schema, &vars, &Context { flag1: true })
-        .await
-        .expect("Execution failed");
-    assert_eq!(errs, []);
-    assert_eq!(
-        result,
-        graphql_value!({
-            "withSelf": true,
-            "independent": 100,
-            "withExecutor": true,
-            "withExecutorAndSelf": true,
-            "withContext": true,
-            "withContextAndSelf": true,
-            "renamed": true,
-            "hasArgument": true,
-            "defaultArgument": true,
-            "argWithDescription": true,
-            "withContextChild": { "ctx": true },
-            "withLifetimeChild": { "value": "blub" },
-            "withMutArg": false,
-        })
-    );
-}
diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs
deleted file mode 100644
index de56473c2..000000000
--- a/juniper/src/macros/tests/impl_subscription.rs
+++ /dev/null
@@ -1,355 +0,0 @@
-use std::pin::Pin;
-
-use futures::StreamExt as _;
-
-use crate::{graphql_value, EmptyMutation, RootNode, Value};
-
-use super::util;
-
-#[derive(Default)]
-struct Context {
-    flag1: bool,
-}
-
-impl crate::Context for Context {}
-
-struct WithLifetime<'a> {
-    value: &'a str,
-}
-
-#[crate::graphql_object(Context = Context)]
-impl<'a> WithLifetime<'a> {
-    fn value(&'a self) -> &'a str {
-        self.value
-    }
-}
-
-struct WithContext;
-
-#[crate::graphql_object(Context = Context)]
-impl WithContext {
-    fn ctx(ctx: &Context) -> bool {
-        ctx.flag1
-    }
-}
-
-#[derive(Default)]
-struct Query;
-
-#[crate::graphql_object(
-    Context = Context,
-)]
-impl Query {
-    fn empty() -> bool {
-        true
-    }
-}
-
-#[derive(Default)]
-struct Mutation;
-
-#[crate::graphql_object(context = Context)]
-impl Mutation {
-    fn empty() -> bool {
-        true
-    }
-}
-
-type Stream<I> = Pin<Box<dyn futures::Stream<Item = I> + Send>>;
-
-#[derive(Default)]
-struct Subscription {
-    b: bool,
-}
-
-#[crate::graphql_subscription(
-    scalar = crate::DefaultScalarValue,
-    name = "Subscription",
-    context = Context,
-)]
-/// Subscription Description.
-impl Subscription {
-    #[graphql(description = "With Self Description")]
-    async fn with_self(&self) -> Stream<bool> {
-        let b = self.b;
-        Box::pin(futures::stream::once(async move { b }))
-    }
-
-    async fn independent() -> Stream<i32> {
-        Box::pin(futures::stream::once(async { 100 }))
-    }
-
-    async fn with_executor(_exec: &Executor<Context>) -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    async fn with_executor_and_self(&self, _exec: &Executor<Context>) -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    async fn with_context(_context: &Context) -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    async fn with_context_and_self(&self, _context: &Context) -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    #[graphql(name = "renamed")]
-    async fn has_custom_name() -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    #[graphql(description = "attr")]
-    async fn has_description_attr() -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    /// Doc description
-    async fn has_description_doc_comment() -> Stream<bool> {
-        Box::pin(futures::stream::once(async { true }))
-    }
-
-    async fn has_argument(arg1: bool) -> Stream<bool> {
-        Box::pin(futures::stream::once(async move { arg1 }))
-    }
-
-    #[graphql(arguments(default_arg(default = true)))]
-    async fn default_argument(default_arg: bool) -> Stream<bool> {
-        Box::pin(futures::stream::once(async move { default_arg }))
-    }
-
-    #[graphql(arguments(arg(description = "my argument description")))]
-    async fn arg_with_description(arg: bool) -> Stream<bool> {
-        Box::pin(futures::stream::once(async move { arg }))
-    }
-
-    async fn with_context_child(&self) -> Stream<WithContext> {
-        Box::pin(futures::stream::once(async { WithContext }))
-    }
-
-    async fn with_implicit_lifetime_child(&self) -> Stream<WithLifetime<'_>> {
-        Box::pin(futures::stream::once(async {
-            WithLifetime { value: "blub" }
-        }))
-    }
-
-    async fn with_mut_arg(mut arg: bool) -> Stream<bool> {
-        if arg {
-            arg = !arg;
-        }
-
-        Box::pin(futures::stream::once(async move { arg }))
-    }
-
-    async fn without_type_alias() -> Pin<Box<dyn futures::Stream<Item = &str> + Send>> {
-        Box::pin(futures::stream::once(async { "abc" }))
-    }
-}
-
-#[tokio::test]
-async fn object_introspect() {
-    let res = util::run_info_query::<Query, Mutation, Subscription>("Subscription").await;
-    assert_eq!(
-        res,
-        crate::graphql_value!({
-            "name": "Subscription",
-            "description": "Subscription Description.",
-            "fields": [
-                {
-                    "name": "withSelf",
-                    "description": "With Self Description",
-                    "args": [],
-                },
-                {
-                    "name": "independent",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withExecutor",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withExecutorAndSelf",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withContext",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withContextAndSelf",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "renamed",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "hasDescriptionAttr",
-                    "description": "attr",
-                    "args": [],
-                },
-                {
-                    "name": "hasDescriptionDocComment",
-                    "description": "Doc description",
-                    "args": [],
-                },
-                {
-                    "name": "hasArgument",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "arg1",
-                            "description": None,
-                            "type": {
-                                "name": None,
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "defaultArgument",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "defaultArg",
-                            "description": None,
-                            "type": {
-                                "name": "Boolean",
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "argWithDescription",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "arg",
-                            "description": "my argument description",
-                            "type": {
-                                "name": None
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "withContextChild",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withImplicitLifetimeChild",
-                    "description": None,
-                    "args": [],
-                },
-                {
-                    "name": "withMutArg",
-                    "description": None,
-                    "args": [
-                        {
-                            "name": "arg",
-                            "description": None,
-                            "type": {
-                                "name": None,
-                            },
-                        }
-                    ],
-                },
-                {
-                    "name": "withoutTypeAlias",
-                    "description": None,
-                    "args": [],
-                }
-            ]
-        })
-    );
-}
-
-#[tokio::test]
-async fn object_query() {
-    let doc = r#"
-    subscription {
-        withSelf
-        independent
-        withExecutor
-        withExecutorAndSelf
-        withContext
-        withContextAndSelf
-        renamed
-        hasArgument(arg1: true)
-        defaultArgument
-        argWithDescription(arg: true)
-        withContextChild {
-            ctx
-        }
-        withImplicitLifetimeChild {
-            value
-        }
-        withMutArg(arg: true)
-        withoutTypeAlias
-    }
-    "#;
-    let schema = RootNode::new(
-        Query,
-        EmptyMutation::<Context>::new(),
-        Subscription { b: true },
-    );
-    let vars = std::collections::HashMap::new();
-
-    let (stream_val, errs) =
-        crate::resolve_into_stream(doc, None, &schema, &vars, &Context { flag1: true })
-            .await
-            .expect("Execution failed");
-
-    let result = if let Value::Object(obj) = stream_val {
-        let mut result = Vec::new();
-        for (name, mut val) in obj {
-            if let Value::Scalar(ref mut stream) = val {
-                let first = stream
-                    .next()
-                    .await
-                    .expect("Stream does not have the first element")
-                    .expect(&format!("Error resolving {} field", name));
-                result.push((name, first))
-            }
-        }
-        result
-    } else {
-        panic!("Expected to get Value::Object ")
-    };
-
-    assert_eq!(errs, []);
-    assert_eq!(
-        result,
-        vec![
-            ("withSelf".to_string(), graphql_value!(true)),
-            ("independent".to_string(), graphql_value!(100)),
-            ("withExecutor".to_string(), graphql_value!(true)),
-            ("withExecutorAndSelf".to_string(), graphql_value!(true)),
-            ("withContext".to_string(), graphql_value!(true)),
-            ("withContextAndSelf".to_string(), graphql_value!(true)),
-            ("renamed".to_string(), graphql_value!(true)),
-            ("hasArgument".to_string(), graphql_value!(true)),
-            ("defaultArgument".to_string(), graphql_value!(true)),
-            ("argWithDescription".to_string(), graphql_value!(true)),
-            (
-                "withContextChild".to_string(),
-                graphql_value!({"ctx": true})
-            ),
-            (
-                "withImplicitLifetimeChild".to_string(),
-                graphql_value!({ "value": "blub" })
-            ),
-            ("withMutArg".to_string(), graphql_value!(false)),
-            ("withoutTypeAlias".to_string(), graphql_value!("abc")),
-        ]
-    );
-}
diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs
deleted file mode 100644
index 0f2eb3a57..000000000
--- a/juniper/src/macros/tests/interface.rs
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
-
-Syntax to validate:
-
-* Order of items: fields, description, instance resolvers
-* Optional Generics/lifetimes
-* Custom name vs. default name
-* Optional commas between items
-* Optional trailing commas on instance resolvers
-
-*/
-
-use crate::{
-    ast::InputValue,
-    graphql_interface, graphql_object,
-    schema::model::RootNode,
-    types::scalars::{EmptyMutation, EmptySubscription},
-    value::{DefaultScalarValue, Object, Value},
-};
-
-struct Concrete;
-
-#[graphql_object(impl = [
-    CustomNameValue, DescriptionValue, WithLifetimeValue<'_>, WithGenericsValue<()>,
-])]
-impl Concrete {
-    fn simple() -> i32 {
-        0
-    }
-}
-
-#[graphql_interface(for = Concrete, name = "ACustomNamedInterface")]
-trait CustomName {
-    fn simple(&self) -> i32;
-}
-#[graphql_interface]
-impl CustomName for Concrete {
-    fn simple(&self) -> i32 {
-        0
-    }
-}
-
-#[graphql_interface(for = Concrete)]
-trait WithLifetime<'a> {
-    fn simple(&self) -> i32;
-}
-#[graphql_interface]
-impl<'a> WithLifetime<'a> for Concrete {
-    fn simple(&self) -> i32 {
-        0
-    }
-}
-
-#[graphql_interface(for = Concrete)]
-trait WithGenerics<T> {
-    fn simple(&self) -> i32;
-}
-#[graphql_interface]
-impl<T> WithGenerics<T> for Concrete {
-    fn simple(&self) -> i32 {
-        0
-    }
-}
-
-#[graphql_interface(for = Concrete, desc = "A description")]
-trait Description {
-    fn simple(&self) -> i32;
-}
-#[graphql_interface]
-impl Description for Concrete {
-    fn simple(&self) -> i32 {
-        0
-    }
-}
-
-struct Root;
-
-#[graphql_object]
-impl Root {
-    fn custom_name() -> CustomNameValue {
-        Concrete.into()
-    }
-
-    fn with_lifetime() -> WithLifetimeValue<'static> {
-        Concrete.into()
-    }
-    fn with_generics() -> WithGenericsValue<i32> {
-        Concrete.into()
-    }
-
-    fn description() -> DescriptionValue {
-        Concrete.into()
-    }
-}
-
-async fn run_type_info_query<F>(type_name: &str, f: F)
-where
-    F: Fn(&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>) -> (),
-{
-    let doc = r#"
-    query ($typeName: String!) {
-        __type(name: $typeName) {
-            name
-            description
-            fields(includeDeprecated: true) {
-                name
-            }
-        }
-    }
-    "#;
-    let schema = RootNode::new(
-        Root {},
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-    let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))]
-        .into_iter()
-        .collect();
-
-    let (result, errs) = crate::execute(doc, None, &schema, &vars, &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let fields = type_info
-        .get_field_value("fields")
-        .expect("fields field missing")
-        .as_list_value()
-        .expect("fields field not a list value");
-
-    f(type_info, fields);
-}
-
-#[tokio::test]
-async fn introspect_custom_name() {
-    run_type_info_query("ACustomNamedInterface", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("ACustomNamedInterface"))
-        );
-        assert_eq!(object.get_field_value("description"), Some(&Value::null()));
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_with_lifetime() {
-    run_type_info_query("WithLifetime", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("WithLifetime"))
-        );
-        assert_eq!(object.get_field_value("description"), Some(&Value::null()));
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_with_generics() {
-    run_type_info_query("WithGenerics", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("WithGenerics"))
-        );
-        assert_eq!(object.get_field_value("description"), Some(&Value::null()));
-
-        assert!(fields.contains(&Value::object(
-            vec![("name", Value::scalar("simple"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_description_first() {
-    run_type_info_query("Description", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("Description"))
-        );
-        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;
-}
diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs
deleted file mode 100644
index 795133254..000000000
--- a/juniper/src/macros/tests/mod.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-mod args;
-mod field;
-mod impl_object;
-mod impl_subscription;
-mod interface;
-mod object;
-mod union;
-mod util;
diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs
deleted file mode 100644
index 8a10ec644..000000000
--- a/juniper/src/macros/tests/object.rs
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-Syntax to validate:
-
-* Order of items: fields, description, interfaces
-* Optional Generics/lifetimes
-* Custom name vs. default name
-* Optional commas between items
-* Nullable/fallible context switching
-*/
-
-#![allow(dead_code)]
-
-use std::marker::PhantomData;
-
-use crate::{
-    ast::InputValue,
-    executor::{Context, FieldResult},
-    graphql_interface, graphql_object,
-    schema::model::RootNode,
-    types::scalars::{EmptyMutation, EmptySubscription},
-    value::{DefaultScalarValue, Object, Value},
-    GraphQLObject,
-};
-
-struct CustomName;
-
-#[graphql_object(name = "ACustomNamedType")]
-impl CustomName {
-    fn simple() -> i32 {
-        0
-    }
-}
-
-struct WithLifetime<'a> {
-    data: PhantomData<&'a i32>,
-}
-
-#[graphql_object]
-impl<'a> WithLifetime<'a> {
-    fn simple() -> i32 {
-        0
-    }
-}
-
-struct WithGenerics<T> {
-    data: T,
-}
-
-#[graphql_object]
-impl<T> WithGenerics<T> {
-    fn simple() -> i32 {
-        0
-    }
-}
-
-#[graphql_interface(for = SimpleObject)]
-trait Interface {
-    fn simple(&self) -> i32 {
-        0
-    }
-}
-
-#[derive(GraphQLObject)]
-#[graphql(impl = InterfaceValue, description = "A description")]
-struct SimpleObject {
-    simple: i32,
-}
-
-#[graphql_interface]
-impl Interface for SimpleObject {}
-
-struct InnerContext;
-impl Context for InnerContext {}
-
-#[derive(GraphQLObject)]
-#[graphql(context = InnerContext)]
-struct InnerType {
-    a: i32,
-}
-
-struct CtxSwitcher;
-
-#[graphql_object(context = InnerContext)]
-impl CtxSwitcher {
-    fn ctx_switch_always() -> (&InnerContext, InnerType) {
-        (executor.context(), InnerType { a: 0 })
-    }
-
-    fn ctx_switch_opt() -> Option<(&InnerContext, InnerType)> {
-        Some((executor.context(), InnerType { a: 0 }))
-    }
-
-    fn ctx_switch_res() -> FieldResult<(&InnerContext, InnerType)> {
-        Ok((executor.context(), InnerType { a: 0 }))
-    }
-
-    fn ctx_switch_res_opt() -> FieldResult<Option<(&InnerContext, InnerType)>> {
-        Ok(Some((executor.context(), InnerType { a: 0 })))
-    }
-}
-
-struct Root;
-
-#[graphql_object(context = InnerContext)]
-impl Root {
-    fn custom_name() -> CustomName {
-        CustomName {}
-    }
-
-    fn with_lifetime() -> WithLifetime<'static> {
-        WithLifetime { data: PhantomData }
-    }
-    fn with_generics() -> WithGenerics<i32> {
-        WithGenerics { data: 123 }
-    }
-
-    fn description_first() -> SimpleObject {
-        SimpleObject { simple: 0 }
-    }
-    fn interface() -> InterfaceValue {
-        SimpleObject { simple: 0 }.into()
-    }
-
-    fn ctx_switcher() -> CtxSwitcher {
-        CtxSwitcher {}
-    }
-}
-
-fn run_type_info_query<F>(type_name: &str, f: F)
-where
-    F: Fn(&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>) -> (),
-{
-    let doc = r#"
-    query ($typeName: String!) {
-        __type(name: $typeName) {
-            name
-            description
-            fields(includeDeprecated: true) {
-                name
-                type {
-                    kind
-                    name
-                    ofType {
-                        kind
-                        name
-                    }
-                }
-            }
-            interfaces {
-                name
-                kind
-            }
-        }
-    }
-    "#;
-    let schema = RootNode::new(
-        Root {},
-        EmptyMutation::<InnerContext>::new(),
-        EmptySubscription::<InnerContext>::new(),
-    );
-    let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))]
-        .into_iter()
-        .collect();
-
-    let (result, errs) =
-        crate::execute_sync(doc, None, &schema, &vars, &InnerContext).expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let fields = type_info
-        .get_field_value("fields")
-        .expect("fields field missing")
-        .as_list_value()
-        .expect("fields field not a list value");
-
-    f(type_info, fields);
-}
-
-#[test]
-fn introspect_custom_name() {
-    run_type_info_query("ACustomNamedType", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("ACustomNamedType"))
-        );
-        assert_eq!(object.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            object.get_field_value("interfaces"),
-            Some(&Value::list(vec![]))
-        );
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "simple",
-            "type": {
-                "kind": "NON_NULL",
-                "name": None,
-                "ofType": { "kind": "SCALAR", "name": "Int" }
-            }
-        })));
-    });
-}
-
-#[test]
-fn introspect_with_lifetime() {
-    run_type_info_query("WithLifetime", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("WithLifetime"))
-        );
-        assert_eq!(object.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            object.get_field_value("interfaces"),
-            Some(&Value::list(vec![]))
-        );
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "simple",
-            "type": {
-                "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" }
-            }
-        })));
-    });
-}
-
-#[test]
-fn introspect_with_generics() {
-    run_type_info_query("WithGenerics", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("WithGenerics"))
-        );
-        assert_eq!(object.get_field_value("description"), Some(&Value::null()));
-        assert_eq!(
-            object.get_field_value("interfaces"),
-            Some(&Value::list(vec![]))
-        );
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "simple",
-            "type": {
-                "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" }
-            }
-        })));
-    });
-}
-
-#[test]
-fn introspect_simple_object() {
-    run_type_info_query("SimpleObject", |object, fields| {
-        assert_eq!(
-            object.get_field_value("name"),
-            Some(&Value::scalar("SimpleObject"))
-        );
-        assert_eq!(
-            object.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-        assert_eq!(
-            object.get_field_value("interfaces"),
-            Some(&Value::list(vec![Value::object(
-                vec![
-                    ("name", Value::scalar("Interface")),
-                    ("kind", Value::scalar("INTERFACE")),
-                ]
-                .into_iter()
-                .collect(),
-            )]))
-        );
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "simple",
-            "type": {
-                "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" }
-            }
-        })));
-    });
-}
-
-#[test]
-fn introspect_ctx_switch() {
-    run_type_info_query("CtxSwitcher", |_, fields| {
-        assert!(fields.contains(&graphql_value!({
-            "name": "ctxSwitchAlways",
-            "type": {
-                "kind": "NON_NULL",
-                "name": None,
-                "ofType": {
-                    "kind": "OBJECT",
-                    "name": "InnerType",
-                }
-            }
-        })));
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "ctxSwitchOpt",
-            "type": {
-                "kind": "OBJECT",
-                "name": "InnerType",
-                "ofType": None
-            }
-        })));
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "ctxSwitchRes",
-            "type": {
-                "kind": "NON_NULL",
-                "name": None,
-                "ofType": {
-                    "kind": "OBJECT",
-                    "name": "InnerType",
-                }
-            }
-        })));
-
-        assert!(fields.contains(&graphql_value!({
-            "name": "ctxSwitchResOpt",
-            "type": {
-                "kind": "OBJECT",
-                "name": "InnerType",
-                "ofType": None
-            }
-        })));
-    });
-}
diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs
deleted file mode 100644
index f36010b65..000000000
--- a/juniper/src/macros/tests/union.rs
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
-
-Syntax to validate:
-
-* Order of items: description, instance resolvers
-* Optional Generics/lifetimes
-* Custom name vs. default name
-* Optional commas between items
-* Optional trailing commas on instance resolvers
-*
-*/
-
-use std::marker::PhantomData;
-
-use crate::{
-    ast::InputValue,
-    graphql_object,
-    schema::model::RootNode,
-    types::scalars::{EmptyMutation, EmptySubscription},
-    value::{DefaultScalarValue, Object, Value},
-    GraphQLUnion,
-};
-
-struct Concrete;
-
-#[graphql_object]
-impl Concrete {
-    fn simple() -> i32 {
-        123
-    }
-}
-
-#[derive(GraphQLUnion)]
-#[graphql(name = "ACustomNamedUnion")]
-enum CustomName {
-    Concrete(Concrete),
-}
-
-#[derive(GraphQLUnion)]
-#[graphql(on Concrete = WithLifetime::resolve)]
-enum WithLifetime<'a> {
-    #[graphql(ignore)]
-    Int(PhantomData<&'a i32>),
-}
-
-impl<'a> WithLifetime<'a> {
-    fn resolve(&self, _: &()) -> Option<&Concrete> {
-        if matches!(self, Self::Int(_)) {
-            Some(&Concrete)
-        } else {
-            None
-        }
-    }
-}
-
-#[derive(GraphQLUnion)]
-#[graphql(on Concrete = WithGenerics::resolve)]
-enum WithGenerics<T> {
-    #[graphql(ignore)]
-    Generic(T),
-}
-
-impl<T> WithGenerics<T> {
-    fn resolve(&self, _: &()) -> Option<&Concrete> {
-        if matches!(self, Self::Generic(_)) {
-            Some(&Concrete)
-        } else {
-            None
-        }
-    }
-}
-
-#[derive(GraphQLUnion)]
-#[graphql(description = "A description")]
-enum DescriptionFirst {
-    Concrete(Concrete),
-}
-
-struct Root;
-
-#[graphql_object]
-impl Root {
-    fn custom_name() -> CustomName {
-        CustomName::Concrete(Concrete)
-    }
-    fn with_lifetime() -> WithLifetime<'_> {
-        WithLifetime::Int(PhantomData)
-    }
-    fn with_generics() -> WithGenerics<i32> {
-        WithGenerics::Generic(123)
-    }
-    fn description_first() -> DescriptionFirst {
-        DescriptionFirst::Concrete(Concrete)
-    }
-}
-
-async fn run_type_info_query<F>(type_name: &str, f: F)
-where
-    F: Fn(&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>) -> (),
-{
-    let doc = r#"
-    query ($typeName: String!) {
-        __type(name: $typeName) {
-            name
-            description
-            possibleTypes {
-                name
-            }
-        }
-    }
-    "#;
-    let schema = RootNode::new(
-        Root {},
-        EmptyMutation::<()>::new(),
-        EmptySubscription::<()>::new(),
-    );
-    let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))]
-        .into_iter()
-        .collect();
-
-    let (result, errs) = crate::execute(doc, None, &schema, &vars, &())
-        .await
-        .expect("Execution failed");
-
-    assert_eq!(errs, []);
-
-    println!("Result: {:#?}", result);
-
-    let type_info = result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .as_object_value()
-        .expect("__type field not an object value");
-
-    let possible_types = type_info
-        .get_field_value("possibleTypes")
-        .expect("possibleTypes field missing")
-        .as_list_value()
-        .expect("possibleTypes field not a list value");
-
-    f(type_info, possible_types);
-}
-
-#[tokio::test]
-async fn introspect_custom_name() {
-    run_type_info_query("ACustomNamedUnion", |union, possible_types| {
-        assert_eq!(
-            union.get_field_value("name"),
-            Some(&Value::scalar("ACustomNamedUnion"))
-        );
-        assert_eq!(union.get_field_value("description"), Some(&Value::null()));
-
-        assert!(possible_types.contains(&Value::object(
-            vec![("name", Value::scalar("Concrete"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_with_lifetime() {
-    run_type_info_query("WithLifetime", |union, possible_types| {
-        assert_eq!(
-            union.get_field_value("name"),
-            Some(&Value::scalar("WithLifetime"))
-        );
-        assert_eq!(union.get_field_value("description"), Some(&Value::null()));
-
-        assert!(possible_types.contains(&Value::object(
-            vec![("name", Value::scalar("Concrete"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_with_generics() {
-    run_type_info_query("WithGenerics", |union, possible_types| {
-        assert_eq!(
-            union.get_field_value("name"),
-            Some(&Value::scalar("WithGenerics"))
-        );
-        assert_eq!(union.get_field_value("description"), Some(&Value::null()));
-
-        assert!(possible_types.contains(&Value::object(
-            vec![("name", Value::scalar("Concrete"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
-
-#[tokio::test]
-async fn introspect_description_first() {
-    run_type_info_query("DescriptionFirst", |union, possible_types| {
-        assert_eq!(
-            union.get_field_value("name"),
-            Some(&Value::scalar("DescriptionFirst"))
-        );
-        assert_eq!(
-            union.get_field_value("description"),
-            Some(&Value::scalar("A description"))
-        );
-
-        assert!(possible_types.contains(&Value::object(
-            vec![("name", Value::scalar("Concrete"))]
-                .into_iter()
-                .collect(),
-        )));
-    })
-    .await;
-}
diff --git a/juniper/src/macros/tests/util.rs b/juniper/src/macros/tests/util.rs
deleted file mode 100644
index 42ccadd4c..000000000
--- a/juniper/src/macros/tests/util.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use crate::{DefaultScalarValue, GraphQLType, GraphQLTypeAsync, RootNode, Value, Variables};
-
-pub async fn run_query<Query, Mutation, Subscription>(query: &str) -> Value
-where
-    Query: GraphQLTypeAsync<DefaultScalarValue, TypeInfo = ()> + Default,
-    Query::Context: Default + Sync,
-    Mutation:
-        GraphQLTypeAsync<DefaultScalarValue, TypeInfo = (), Context = Query::Context> + Default,
-    Subscription:
-        GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Query::Context> + Default + Sync,
-{
-    let schema = RootNode::new(
-        Query::default(),
-        Mutation::default(),
-        Subscription::default(),
-    );
-    let (result, errs) = crate::execute(
-        query,
-        None,
-        &schema,
-        &Variables::new(),
-        &Query::Context::default(),
-    )
-    .await
-    .expect("Execution failed");
-
-    assert_eq!(errs, []);
-    result
-}
-
-pub async fn run_info_query<Query, Mutation, Subscription>(type_name: &str) -> Value
-where
-    Query: GraphQLTypeAsync<DefaultScalarValue, TypeInfo = ()> + Default,
-    Query::Context: Default + Sync,
-    Mutation:
-        GraphQLTypeAsync<DefaultScalarValue, TypeInfo = (), Context = Query::Context> + Default,
-    Subscription:
-        GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Query::Context> + Default + Sync,
-{
-    let query = format!(
-        r#"
-    {{
-        __type(name: "{}") {{
-            name,
-            description,
-            fields {{
-                name
-                description
-                args {{
-                    name
-                    description
-                    type {{
-                        name
-                    }}
-                }}
-            }}
-        }}
-    }}
-    "#,
-        type_name
-    );
-    let result = run_query::<Query, Mutation, Subscription>(&query).await;
-    result
-        .as_object_value()
-        .expect("Result is not an object")
-        .get_field_value("__type")
-        .expect("__type field missing")
-        .clone()
-}
diff --git a/juniper/src/parser/tests/value.rs b/juniper/src/parser/tests/value.rs
index fe425cd1e..c8b1ac9b9 100644
--- a/juniper/src/parser/tests/value.rs
+++ b/juniper/src/parser/tests/value.rs
@@ -30,11 +30,8 @@ struct Foo {
 
 struct Query;
 
-#[crate::graphql_object(Scalar = S)]
-impl<'a, S> Query
-where
-    S: crate::ScalarValue + 'a,
-{
+#[crate::graphql_object]
+impl Query {
     fn int_field() -> i32 {
         42
     }
diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs
index 4b84568cb..3b220bdd5 100644
--- a/juniper/src/tests/fixtures/starwars/schema.rs
+++ b/juniper/src/tests/fixtures/starwars/schema.rs
@@ -9,21 +9,26 @@ pub struct Query;
 #[graphql_object(context = Database)]
 /// 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> {
+    fn human(
+        #[graphql(context)] database: &Database,
+        #[graphql(description = "id of the human")] id: String,
+    ) -> Option<&Human> {
         database.get_human(&id)
     }
 
-    #[graphql(arguments(id(description = "id of the droid")))]
-    fn droid(database: &Database, id: String) -> Option<&Droid> {
+    fn droid(
+        #[graphql(context)] database: &Database,
+        #[graphql(description = "id of the droid")] id: String,
+    ) -> Option<&Droid> {
         database.get_droid(&id)
     }
 
-    #[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> {
+    fn hero(
+        #[graphql(context)] database: &Database,
+        #[graphql(description = "If omitted, returns the hero of the whole saga. \
+                                 If provided, returns the hero of that particular episode")]
+        episode: Option<Episode>,
+    ) -> Option<CharacterValue> {
         Some(database.get_hero(episode))
     }
 }
diff --git a/juniper/src/tests/subscriptions.rs b/juniper/src/tests/subscriptions.rs
index 66d118fcb..ac61d504e 100644
--- a/juniper/src/tests/subscriptions.rs
+++ b/juniper/src/tests/subscriptions.rs
@@ -1,10 +1,10 @@
 use std::{iter, iter::FromIterator as _, pin::Pin};
 
-use futures::{self, StreamExt as _};
+use futures::{stream, StreamExt as _};
 
 use crate::{
-    http::GraphQLRequest, Context, DefaultScalarValue, EmptyMutation, ExecutionError, FieldError,
-    GraphQLObject, Object, RootNode, Value,
+    graphql_object, graphql_subscription, http::GraphQLRequest, Context, DefaultScalarValue,
+    EmptyMutation, ExecutionError, FieldError, GraphQLObject, Object, RootNode, Value,
 };
 
 #[derive(Debug, Clone)]
@@ -22,7 +22,7 @@ struct Human {
 
 struct MyQuery;
 
-#[crate::graphql_object(context = MyContext)]
+#[graphql_object(context = MyContext)]
 impl MyQuery {
     fn test(&self) -> i32 {
         0 // NOTICE: does not serve a purpose
@@ -42,10 +42,10 @@ type HumanStream = Pin<Box<dyn futures::Stream<Item = Human> + Send>>;
 
 struct MySubscription;
 
-#[crate::graphql_subscription(context = MyContext)]
+#[graphql_subscription(context = MyContext)]
 impl MySubscription {
     async fn async_human() -> HumanStream {
-        Box::pin(futures::stream::once(async {
+        Box::pin(stream::once(async {
             Human {
                 id: "stream id".to_string(),
                 name: "stream name".to_string(),
@@ -61,9 +61,9 @@ impl MySubscription {
         ))
     }
 
-    async fn human_with_context(ctxt: &MyContext) -> HumanStream {
-        let context_val = ctxt.0.clone();
-        Box::pin(futures::stream::once(async move {
+    async fn human_with_context(context: &MyContext) -> HumanStream {
+        let context_val = context.0.clone();
+        Box::pin(stream::once(async move {
             Human {
                 id: context_val.to_string(),
                 name: context_val.to_string(),
@@ -73,7 +73,7 @@ impl MySubscription {
     }
 
     async fn human_with_args(id: String, name: String) -> HumanStream {
-        Box::pin(futures::stream::once(async {
+        Box::pin(stream::once(async {
             Human {
                 id,
                 name,