From 927e42201aa5ec14a2a454915563c73c78df2704 Mon Sep 17 00:00:00 2001
From: ilslv <47687266+ilslv@users.noreply.github.com>
Date: Tue, 28 Jun 2022 14:27:28 +0300
Subject: [PATCH] Rework `#[derive(GraphQLInputObject)]` macro implementation
 (#1052)

Co-authored-by: Kai Ren <tyranron@gmail.com>
---
 .../introspection/input_object.rs             |   4 +-
 juniper/src/executor_tests/variables.rs       |   2 +-
 juniper_codegen/src/common/default.rs         |  65 ++
 juniper_codegen/src/common/field/mod.rs       |   8 +-
 juniper_codegen/src/common/mod.rs             |   1 +
 juniper_codegen/src/derive_input_object.rs    | 151 ----
 juniper_codegen/src/graphql_enum/derive.rs    |   3 +-
 juniper_codegen/src/graphql_enum/mod.rs       |  16 +-
 .../src/graphql_input_object/derive.rs        | 136 +++
 .../src/graphql_input_object/mod.rs           | 797 ++++++++++++++++++
 juniper_codegen/src/graphql_interface/mod.rs  |   3 +-
 juniper_codegen/src/graphql_object/mod.rs     |   3 +-
 juniper_codegen/src/graphql_union/mod.rs      |   3 +-
 juniper_codegen/src/lib.rs                    | 115 ++-
 juniper_codegen/src/result.rs                 |  73 +-
 juniper_codegen/src/util/duplicate.rs         |  46 -
 juniper_codegen/src/util/mod.rs               | 712 +---------------
 .../derive_incompatible_field_type.rs         |  13 +
 ... => derive_incompatible_field_type.stderr} |  49 +-
 .../derive_incompatible_object.rs             |  11 -
 .../fail/input-object/derive_no_fields.rs     |   4 +-
 .../fail/input-object/derive_no_fields.stderr |  10 +-
 .../fail/input-object/derive_no_underscore.rs |   4 +-
 .../input-object/derive_no_underscore.stderr  |   7 +-
 .../fail/input-object/derive_unique_name.rs   |   4 +-
 .../input-object/derive_unique_name.stderr    |  17 +-
 .../src/codegen/derive_input_object.rs        | 191 -----
 .../src/codegen/input_object_derive.rs        | 708 ++++++++++++++++
 tests/integration/src/codegen/mod.rs          |   2 +-
 29 files changed, 1928 insertions(+), 1230 deletions(-)
 create mode 100644 juniper_codegen/src/common/default.rs
 delete mode 100644 juniper_codegen/src/derive_input_object.rs
 create mode 100644 juniper_codegen/src/graphql_input_object/derive.rs
 create mode 100644 juniper_codegen/src/graphql_input_object/mod.rs
 delete mode 100644 juniper_codegen/src/util/duplicate.rs
 create mode 100644 tests/codegen/fail/input-object/derive_incompatible_field_type.rs
 rename tests/codegen/fail/input-object/{derive_incompatible_object.stderr => derive_incompatible_field_type.stderr} (50%)
 delete mode 100644 tests/codegen/fail/input-object/derive_incompatible_object.rs
 delete mode 100644 tests/integration/src/codegen/derive_input_object.rs
 create mode 100644 tests/integration/src/codegen/input_object_derive.rs

diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs
index f68b9796..450f774d 100644
--- a/juniper/src/executor_tests/introspection/input_object.rs
+++ b/juniper/src/executor_tests/introspection/input_object.rs
@@ -76,9 +76,9 @@ struct FieldDescription {
 
 #[derive(GraphQLInputObject, Debug)]
 struct FieldWithDefaults {
-    #[graphql(default = "123")]
+    #[graphql(default = 123)]
     field_one: i32,
-    #[graphql(default = "456", description = "The second field")]
+    #[graphql(default = 456, description = "The second field")]
     field_two: i32,
 }
 
diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs
index 257f26c4..023bd61e 100644
--- a/juniper/src/executor_tests/variables.rs
+++ b/juniper/src/executor_tests/variables.rs
@@ -49,7 +49,7 @@ struct ExampleInputObject {
 
 #[derive(GraphQLInputObject, Debug)]
 struct InputWithDefaults {
-    #[graphql(default = "123")]
+    #[graphql(default = 123)]
     a: i32,
 }
 
diff --git a/juniper_codegen/src/common/default.rs b/juniper_codegen/src/common/default.rs
new file mode 100644
index 00000000..03fb68a0
--- /dev/null
+++ b/juniper_codegen/src/common/default.rs
@@ -0,0 +1,65 @@
+//! Common functions, definitions and extensions for parsing and code generation
+//! of [GraphQL default values][0]
+//!
+//! [0]: https://spec.graphql.org/October2021#DefaultValue
+
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{
+    parse::{Parse, ParseStream},
+    token,
+};
+
+use crate::common::parse::ParseBufferExt as _;
+
+/// Representation of a [GraphQL default value][0] for code generation.
+///
+/// [0]: https://spec.graphql.org/October2021#DefaultValue
+#[derive(Clone, Debug)]
+pub(crate) enum Value {
+    /// [`Default`] implementation should be used.
+    Default,
+
+    /// Explicit [`Expr`]ession to be used as the [default value][0].
+    ///
+    /// [`Expr`]: syn::Expr
+    /// [0]: https://spec.graphql.org/October2021#DefaultValue
+    Expr(Box<syn::Expr>),
+}
+
+impl Default for Value {
+    fn default() -> Self {
+        Self::Default
+    }
+}
+
+impl From<Option<syn::Expr>> for Value {
+    fn from(opt: Option<syn::Expr>) -> Self {
+        match opt {
+            Some(expr) => Self::Expr(Box::new(expr)),
+            None => Self::Default,
+        }
+    }
+}
+
+impl Parse for Value {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        Ok(input
+            .try_parse::<token::Eq>()?
+            .map(|_| input.parse::<syn::Expr>())
+            .transpose()?
+            .into())
+    }
+}
+
+impl ToTokens for Value {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        match self {
+            Self::Default => quote! {
+                ::std::default::Default::default()
+            }
+            .to_tokens(into),
+            Self::Expr(expr) => expr.to_tokens(into),
+        }
+    }
+}
diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs
index 2efe969f..d9fc7526 100644
--- a/juniper_codegen/src/common/field/mod.rs
+++ b/juniper_codegen/src/common/field/mod.rs
@@ -1,7 +1,7 @@
 //! Common functions, definitions and extensions for parsing and code generation
 //! of [GraphQL fields][1]
 //!
-//! [1]: https://spec.graphql.org/June2018/#sec-Language.Fields.
+//! [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
 
 pub(crate) mod arg;
 
@@ -42,8 +42,8 @@ pub(crate) struct Attr {
 
     /// Explicitly specified [description][2] of this [GraphQL field][1].
     ///
-    /// If [`None`], then Rust doc comment is used as the [description][2], if
-    /// any.
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
     /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
@@ -51,7 +51,7 @@ pub(crate) struct Attr {
 
     /// Explicitly specified [deprecation][2] of this [GraphQL field][1].
     ///
-    /// If [`None`], then Rust `#[deprecated]` attribute is used as the
+    /// If [`None`], then Rust `#[deprecated]` attribute will be used as the
     /// [deprecation][2], if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs
index fd16d954..e3f72d63 100644
--- a/juniper_codegen/src/common/mod.rs
+++ b/juniper_codegen/src/common/mod.rs
@@ -1,5 +1,6 @@
 //! Common functions, definitions and extensions for code generation, used by this crate.
 
+pub(crate) mod default;
 pub(crate) mod field;
 pub(crate) mod gen;
 pub(crate) mod parse;
diff --git a/juniper_codegen/src/derive_input_object.rs b/juniper_codegen/src/derive_input_object.rs
deleted file mode 100644
index 48d63dbc..00000000
--- a/juniper_codegen/src/derive_input_object.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-#![allow(clippy::match_wild_err_arm)]
-use crate::{
-    result::{GraphQLScope, UnsupportedAttribute},
-    util::{self, span_container::SpanContainer, RenameRule},
-};
-use proc_macro2::TokenStream;
-use quote::{quote, ToTokens};
-use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
-
-pub fn impl_input_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<TokenStream> {
-    let ast_span = ast.span();
-    let fields = match ast.data {
-        Data::Struct(data) => match data.fields {
-            Fields::Named(named) => named.named,
-            _ => {
-                return Err(
-                    error.custom_error(ast_span, "all fields must be named, e.g., `test: String`")
-                )
-            }
-        },
-        _ => return Err(error.custom_error(ast_span, "can only be used on structs with fields")),
-    };
-
-    // Parse attributes.
-    let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
-
-    // Parse attributes.
-    let ident = &ast.ident;
-    let name = attrs
-        .name
-        .clone()
-        .map(SpanContainer::into_inner)
-        .unwrap_or_else(|| ident.to_string());
-
-    let fields = 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;
-                }
-            };
-
-            let field_ident = field.ident.as_ref().unwrap();
-            let name = match field_attrs.name {
-                Some(ref name) => name.to_string(),
-                None => attrs
-                    .rename
-                    .unwrap_or(RenameRule::CamelCase)
-                    .apply(&field_ident.unraw().to_string()),
-            };
-
-            if let Some(span) = field_attrs.skip {
-                error.unsupported_attribute_within(span.span(), UnsupportedAttribute::Skip)
-            }
-
-            if let Some(span) = field_attrs.deprecation {
-                error.unsupported_attribute_within(
-                    span.span_ident(),
-                    UnsupportedAttribute::Deprecation,
-                )
-            }
-
-            if name.starts_with("__") {
-                error.no_double_underscore(if let Some(name) = field_attrs.name {
-                    name.span_ident()
-                } else {
-                    name.span()
-                });
-            }
-
-            let resolver_code = quote!(#field_ident);
-
-            let default = field_attrs
-                .default
-                .map(|default| match default.into_inner() {
-                    Some(expr) => expr.into_token_stream(),
-                    None => quote! { Default::default() },
-                });
-
-            Some(util::GraphQLTypeDefinitionField {
-                name,
-                _type: field.ty,
-                args: Vec::new(),
-                description: field_attrs.description.map(SpanContainer::into_inner),
-                deprecation: None,
-                resolver_code,
-                is_type_inferred: true,
-                is_async: false,
-                default,
-                span,
-            })
-        })
-        .collect::<Vec<_>>();
-
-    proc_macro_error::abort_if_dirty();
-
-    if fields.is_empty() {
-        error.not_empty(ast_span);
-    }
-
-    if let Some(duplicates) =
-        crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name)
-    {
-        error.duplicate(duplicates.iter())
-    }
-
-    if !attrs.interfaces.is_empty() {
-        attrs.interfaces.iter().for_each(|elm| {
-            error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
-        });
-    }
-
-    if let Some(duplicates) =
-        crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
-    {
-        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()
-        });
-    }
-
-    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: vec![],
-        include_type_generics: true,
-        generic_scalar: true,
-        no_async: attrs.no_async.is_some(),
-    };
-
-    Ok(definition.into_input_object_tokens())
-}
diff --git a/juniper_codegen/src/graphql_enum/derive.rs b/juniper_codegen/src/graphql_enum/derive.rs
index 7600226e..03ae30c2 100644
--- a/juniper_codegen/src/graphql_enum/derive.rs
+++ b/juniper_codegen/src/graphql_enum/derive.rs
@@ -1,8 +1,9 @@
 //! Code generation for `#[derive(GraphQLEnum)]` macro.
 
+use std::collections::HashSet;
+
 use proc_macro2::TokenStream;
 use quote::ToTokens as _;
-use std::collections::HashSet;
 use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
 
 use crate::{
diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs
index 8556d8b6..87763e64 100644
--- a/juniper_codegen/src/graphql_enum/mod.rs
+++ b/juniper_codegen/src/graphql_enum/mod.rs
@@ -30,7 +30,7 @@ use crate::{
 };
 
 /// Available arguments behind `#[graphql]` attribute placed on a Rust enum
-/// definition, when generating code for a [GraphQL enum][0] type.
+/// definition, when generating code for a [GraphQL enum][0].
 ///
 /// [0]: https://spec.graphql.org/October2021#sec-Enums
 #[derive(Debug, Default)]
@@ -44,8 +44,8 @@ struct ContainerAttr {
 
     /// Explicitly specified [description][2] of this [GraphQL enum][0].
     ///
-    /// If [`None`], then Rust doc comment will be used as [description][2], if
-    /// any.
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
     ///
     /// [0]: https://spec.graphql.org/October2021#sec-Enums
     /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
@@ -190,14 +190,15 @@ impl ContainerAttr {
 struct VariantAttr {
     /// Explicitly specified name of this [GraphQL enum value][1].
     ///
-    /// If [`None`], then Rust enum variant's name is used by default.
+    /// If [`None`], then Rust enum variant's name will be used by default.
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
     name: Option<SpanContainer<String>>,
 
     /// Explicitly specified [description][2] of this [GraphQL enum value][1].
     ///
-    /// If [`None`], then Rust doc comment is used as [description][2], if any.
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
     ///
     /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
     /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
@@ -205,7 +206,7 @@ struct VariantAttr {
 
     /// Explicitly specified [deprecation][2] of this [GraphQL enum value][1].
     ///
-    /// If [`None`], then Rust `#[deprecated]` attribute is used as the
+    /// If [`None`], then Rust `#[deprecated]` attribute will be used as the
     /// [deprecation][2], if any.
     ///
     /// If the inner [`Option`] is [`None`], then no [reason][3] was provided.
@@ -357,8 +358,9 @@ struct Definition {
     /// [0]: https://spec.graphql.org/October2021#sec-Enums
     ident: syn::Ident,
 
-    /// [`syn::Generics`] of the Rust enum behind this [GraphQL enum][0].
+    /// [`Generics`] of the Rust enum behind this [GraphQL enum][0].
     ///
+    /// [`Generics`]: syn::Generics
     /// [0]: https://spec.graphql.org/October2021#sec-Enums
     generics: syn::Generics,
 
diff --git a/juniper_codegen/src/graphql_input_object/derive.rs b/juniper_codegen/src/graphql_input_object/derive.rs
new file mode 100644
index 00000000..c1c333b1
--- /dev/null
+++ b/juniper_codegen/src/graphql_input_object/derive.rs
@@ -0,0 +1,136 @@
+//! Code generation for `#[derive(GraphQLInputObject)]` macro.
+
+use std::collections::HashSet;
+
+use proc_macro2::TokenStream;
+use quote::ToTokens as _;
+use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
+
+use crate::{
+    common::scalar,
+    result::GraphQLScope,
+    util::{span_container::SpanContainer, RenameRule},
+};
+
+use super::{ContainerAttr, Definition, FieldAttr, FieldDefinition};
+
+/// [`GraphQLScope`] of errors for `#[derive(GraphQLInputObject)]` macro.
+const ERR: GraphQLScope = GraphQLScope::InputObjectDerive;
+
+/// Expands `#[derive(GraphQLInputObject)]` macro into generated code.
+pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
+    let ast = syn::parse2::<syn::DeriveInput>(input)?;
+    let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?;
+
+    let data = if let syn::Data::Struct(data) = &ast.data {
+        data
+    } else {
+        return Err(ERR.custom_error(ast.span(), "can only be derived on structs"));
+    };
+
+    let renaming = attr
+        .rename_fields
+        .map(SpanContainer::into_inner)
+        .unwrap_or(RenameRule::CamelCase);
+
+    let is_internal = attr.is_internal;
+    let fields = data
+        .fields
+        .iter()
+        .filter_map(|f| parse_field(f, renaming, is_internal))
+        .collect::<Vec<_>>();
+
+    proc_macro_error::abort_if_dirty();
+
+    if !fields.iter().any(|f| !f.ignored) {
+        return Err(ERR.custom_error(data.fields.span(), "expected at least 1 non-ignored field"));
+    }
+
+    let unique_fields = fields.iter().map(|v| &v.name).collect::<HashSet<_>>();
+    if unique_fields.len() != fields.len() {
+        return Err(ERR.custom_error(
+            data.fields.span(),
+            "expected all fields to have unique names",
+        ));
+    }
+
+    let name = attr
+        .name
+        .clone()
+        .map(SpanContainer::into_inner)
+        .unwrap_or_else(|| ast.ident.unraw().to_string())
+        .into_boxed_str();
+    if !attr.is_internal && name.starts_with("__") {
+        ERR.no_double_underscore(
+            attr.name
+                .as_ref()
+                .map(SpanContainer::span_ident)
+                .unwrap_or_else(|| ast.ident.span()),
+        );
+    }
+
+    let context = attr
+        .context
+        .map_or_else(|| parse_quote! { () }, SpanContainer::into_inner);
+
+    let description = attr.description.map(|d| d.into_inner().into_boxed_str());
+
+    let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
+
+    proc_macro_error::abort_if_dirty();
+
+    let definition = Definition {
+        ident: ast.ident,
+        generics: ast.generics,
+        name,
+        description,
+        context,
+        scalar,
+        fields,
+    };
+
+    Ok(definition.into_token_stream())
+}
+
+/// Parses a [`FieldDefinition`] from the given struct field definition.
+///
+/// Returns [`None`] if the parsing fails.
+fn parse_field(f: &syn::Field, renaming: RenameRule, is_internal: bool) -> Option<FieldDefinition> {
+    let field_attr = FieldAttr::from_attrs("graphql", &f.attrs)
+        .map_err(|e| proc_macro_error::emit_error!(e))
+        .ok()?;
+
+    let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?;
+
+    let name = field_attr
+        .name
+        .map_or_else(
+            || renaming.apply(&ident.unraw().to_string()),
+            SpanContainer::into_inner,
+        )
+        .into_boxed_str();
+    if !is_internal && name.starts_with("__") {
+        ERR.no_double_underscore(f.span());
+    }
+
+    let default = field_attr.default.map(SpanContainer::into_inner);
+    let description = field_attr
+        .description
+        .map(|d| d.into_inner().into_boxed_str());
+
+    Some(FieldDefinition {
+        ident: ident.clone(),
+        ty: f.ty.clone(),
+        default,
+        name,
+        description,
+        ignored: field_attr.ignore.is_some(),
+    })
+}
+
+/// Emits "expected named struct field" [`syn::Error`] pointing to the given
+/// `span`.
+pub(crate) fn err_unnamed_field<T, S: Spanned>(span: &S) -> Option<T> {
+    ERR.emit_custom(span.span(), "expected named struct field");
+    None
+}
diff --git a/juniper_codegen/src/graphql_input_object/mod.rs b/juniper_codegen/src/graphql_input_object/mod.rs
new file mode 100644
index 00000000..ecb0aaa8
--- /dev/null
+++ b/juniper_codegen/src/graphql_input_object/mod.rs
@@ -0,0 +1,797 @@
+//! Code generation for [GraphQL input objects][0].
+//!
+//! [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+
+pub(crate) mod derive;
+
+use std::convert::TryInto as _;
+
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{
+    ext::IdentExt as _,
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned,
+    token,
+};
+
+use crate::{
+    common::{
+        default,
+        parse::{
+            attr::{err, OptionExt as _},
+            ParseBufferExt as _,
+        },
+        scalar,
+    },
+    util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule},
+};
+
+/// Available arguments behind `#[graphql]` attribute placed on a Rust struct
+/// definition, when generating code for a [GraphQL input object][0].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+#[derive(Debug, Default)]
+struct ContainerAttr {
+    /// Explicitly specified name of this [GraphQL input object][0].
+    ///
+    /// If [`None`], then Rust struct name will be used by default.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    name: Option<SpanContainer<String>>,
+
+    /// Explicitly specified [description][2] of this [GraphQL input object][0].
+    ///
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
+    description: Option<SpanContainer<String>>,
+
+    /// Explicitly specified type of [`Context`] to use for resolving this
+    /// [GraphQL input object][0] type with.
+    ///
+    /// If [`None`], then unit type `()` is assumed as a type of [`Context`].
+    ///
+    /// [`Context`]: juniper::Context
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    context: Option<SpanContainer<syn::Type>>,
+
+    /// Explicitly specified type (or type parameter with its bounds) of
+    /// [`ScalarValue`] to use for resolving this [GraphQL input object][0] type
+    /// with.
+    ///
+    /// If [`None`], then generated code will be generic over any
+    /// [`ScalarValue`] type.
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    scalar: Option<SpanContainer<scalar::AttrValue>>,
+
+    /// Explicitly specified [`RenameRule`] for all fields of this
+    /// [GraphQL input object][0].
+    ///
+    /// If [`None`], then the [`RenameRule::CamelCase`] rule will be
+    /// applied by default.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    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 ContainerAttr {
+    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))?
+                }
+                "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 ContainerAttr {
+    /// Tries to merge two [`ContainerAttr`]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),
+            rename_fields: try_merge_opt!(rename_fields: self, another),
+            is_internal: self.is_internal || another.is_internal,
+        })
+    }
+
+    /// Parses [`ContainerAttr`] 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)
+    }
+}
+
+/// Available arguments behind `#[graphql]` attribute when generating code for
+/// [GraphQL input object][0]'s [field][1].
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+/// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition
+#[derive(Debug, Default)]
+struct FieldAttr {
+    /// Explicitly specified name of this [GraphQL input object field][1].
+    ///
+    /// If [`None`], then Rust struct field name will be used by default.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    name: Option<SpanContainer<String>>,
+
+    /// Explicitly specified [default value][2] of this
+    /// [GraphQL input object field][1] to be used used in case a field value is
+    /// not provided.
+    ///
+    /// If [`None`], the this [field][1] will have no [default value][2].
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    /// [2]: https://spec.graphql.org/October2021#DefaultValue
+    default: Option<SpanContainer<default::Value>>,
+
+    /// Explicitly specified [description][2] of this
+    /// [GraphQL input object field][1].
+    ///
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
+    description: Option<SpanContainer<String>>,
+
+    /// Explicitly specified marker for the Rust struct field to be ignored and
+    /// not included into the code generated for a [GraphQL input object][0]
+    /// implementation.
+    ///
+    /// Ignored Rust struct fields still consider the [`default`] attribute's
+    /// argument.
+    ///
+    /// [`default`]: Self::default
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    ignore: Option<SpanContainer<syn::Ident>>,
+}
+
+impl Parse for FieldAttr {
+    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))?
+                }
+                "default" => {
+                    let val = input.parse::<default::Value>()?;
+                    out.default
+                        .replace(SpanContainer::new(ident.span(), Some(val.span()), val))
+                        .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))?
+                }
+                "ignore" | "skip" => out
+                    .ignore
+                    .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 FieldAttr {
+    /// Tries to merge two [`FieldAttr`]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),
+            default: try_merge_opt!(default: self, another),
+            description: try_merge_opt!(description: self, another),
+            ignore: try_merge_opt!(ignore: self, another),
+        })
+    }
+
+    /// Parses [`FieldAttr`] 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 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)
+    }
+}
+
+/// Representation of a [GraphQL input object field][1] for code generation.
+///
+/// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition
+#[derive(Debug)]
+struct FieldDefinition {
+    /// [`Ident`] of the Rust struct field behind this
+    /// [GraphQL input object field][1].
+    ///
+    /// [`Ident`]: syn::Ident
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    ident: syn::Ident,
+
+    /// Rust type that this [GraphQL input object field][1] is represented with.
+    ///
+    /// It should contain all its generics, if any.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    ty: syn::Type,
+
+    /// [Default value][2] of this [GraphQL input object field][1] to be used in
+    /// case a [field][1] value is not provided.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    /// [2]: https://spec.graphql.org/October2021#DefaultValue
+    default: Option<default::Value>,
+
+    /// Name of this [GraphQL input object field][1] in GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    name: Box<str>,
+
+    /// [Description][2] of this [GraphQL input object field][1] to put into
+    /// GraphQL schema.
+    ///
+    /// [1]: https://spec.graphql.org/October2021#InputValueDefinition
+    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
+    description: Option<Box<str>>,
+
+    /// Indicator whether the Rust struct field behinds this
+    /// [GraphQL input object field][1] is being ignored and should not be
+    /// included into the generated code.
+    ///
+    /// Ignored Rust struct fields still consider the [`default`] attribute's
+    /// argument.
+    ///
+    /// [`default`]: Self::default
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    ignored: bool,
+}
+
+/// Representation of [GraphQL input object][0] for code generation.
+///
+/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+#[derive(Debug)]
+struct Definition {
+    /// [`Ident`] of the Rust struct behind this [GraphQL input object][0].
+    ///
+    /// [`Ident`]: syn::Ident
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    ident: syn::Ident,
+
+    /// [`Generics`] of the Rust enum behind this [GraphQL input object][0].
+    ///
+    /// [`Generics`]: syn::Generics
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    generics: syn::Generics,
+
+    /// Name of this [GraphQL input object][0] in GraphQL schema.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    name: Box<str>,
+
+    /// [Description][2] of this [GraphQL input object][0] to put into GraphQL
+    /// schema.
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
+    description: Option<Box<str>>,
+
+    /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
+    /// for this [GraphQL input object][0].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`Context`]: juniper::Context
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    context: syn::Type,
+
+    /// [`ScalarValue`] parametrization to generate [`GraphQLType`]
+    /// implementation with for this [GraphQL input object][0].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [`ScalarValue`]: juniper::ScalarValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    scalar: scalar::Type,
+
+    /// [Fields][1] of this [GraphQL input object][0].
+    ///
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    /// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition
+    fields: Vec<FieldDefinition>,
+}
+
+impl ToTokens for Definition {
+    fn to_tokens(&self, into: &mut TokenStream) {
+        self.impl_input_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_from_input_value_tokens().to_tokens(into);
+        self.impl_to_input_value_tokens().to_tokens(into);
+        self.impl_reflection_traits_tokens().to_tokens(into);
+    }
+}
+
+impl Definition {
+    /// Returns generated code implementing [`marker::IsInputType`] trait for
+    /// this [GraphQL input object][0].
+    ///
+    /// [`marker::IsInputType`]: juniper::marker::IsInputType
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_input_type_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let scalar = &self.scalar;
+
+        let generics = self.impl_generics(false);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        let assert_fields_input_values = self.fields.iter().filter_map(|f| {
+            let ty = &f.ty;
+
+            (!f.ignored).then(|| {
+                quote! {
+                    <#ty as ::juniper::marker::IsInputType<#scalar>>::mark();
+                }
+            })
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::marker::IsInputType<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                fn mark() {
+                    #( #assert_fields_input_values )*
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLType`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`GraphQLType`]: juniper::GraphQLType
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_graphql_type_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let scalar = &self.scalar;
+        let name = &self.name;
+
+        let generics = self.impl_generics(false);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        let description = self
+            .description
+            .as_ref()
+            .map(|desc| quote! { .description(#desc) });
+
+        let fields = self.fields.iter().filter_map(|f| {
+            let ty = &f.ty;
+            let name = &f.name;
+
+            (!f.ignored).then(|| {
+                let arg = if let Some(default) = &f.default {
+                    quote! { .arg_with_default::<#ty>(#name, &#default, info) }
+                } else {
+                    quote! { .arg::<#ty>(#name, info) }
+                };
+                let description = f
+                    .description
+                    .as_ref()
+                    .map(|desc| quote! { .description(#desc) });
+
+                quote! { registry#arg#description }
+            })
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLType<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                fn name(_: &Self::TypeInfo) -> Option<&'static str> {
+                    Some(#name)
+                }
+
+                fn meta<'r>(
+                    info: &Self::TypeInfo,
+                    registry: &mut ::juniper::Registry<'r, #scalar>,
+                ) -> ::juniper::meta::MetaType<'r, #scalar>
+                where
+                    #scalar: 'r,
+                {
+                    let fields = [#( #fields ),*];
+                    registry
+                        .build_input_object_type::<#ident#ty_generics>(info, &fields)
+                        #description
+                        .into_meta()
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLValue`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`GraphQLValue`]: juniper::GraphQLValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_graphql_value_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let scalar = &self.scalar;
+        let context = &self.context;
+
+        let generics = self.impl_generics(false);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLValue<#scalar>
+                for #ident#ty_generics
+                #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)
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_graphql_value_async_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let scalar = &self.scalar;
+
+        let generics = self.impl_generics(true);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        quote! {
+            #[allow(non_snake_case)]
+            #[automatically_derived]
+            impl#impl_generics ::juniper::GraphQLValueAsync<#scalar>
+                for #ident#ty_generics
+                #where_clause {}
+        }
+    }
+
+    /// Returns generated code implementing [`FromInputValue`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`FromInputValue`]: juniper::FromInputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_from_input_value_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let scalar = &self.scalar;
+
+        let generics = self.impl_generics(false);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        let fields = self.fields.iter().map(|f| {
+            let ident = &f.ident;
+
+            let construct = if f.ignored {
+                f.default.as_ref().map_or_else(
+                    || {
+                        let expr = default::Value::default();
+                        quote! { #expr }
+                    },
+                    |expr| quote! { #expr },
+                )
+            } else {
+                let name = &f.name;
+
+                let fallback = f.default.as_ref().map_or_else(
+                    || {
+                        quote! {
+                            ::juniper::FromInputValue::<#scalar>::from_implicit_null()
+                                .map_err(::juniper::IntoFieldError::into_field_error)?
+                        }
+                    },
+                    |expr| quote! { #expr },
+                );
+
+                quote! {
+                    match obj.get(#name) {
+                        Some(v) => {
+                            ::juniper::FromInputValue::<#scalar>::from_input_value(v)
+                                .map_err(::juniper::IntoFieldError::into_field_error)?
+                        }
+                        None => { #fallback }
+                    }
+                }
+            };
+
+            quote! { #ident: { #construct }, }
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::FromInputValue<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                type Error = ::juniper::FieldError<#scalar>;
+
+                fn from_input_value(
+                    value: &::juniper::InputValue<#scalar>,
+                ) -> Result<Self, Self::Error> {
+                    let obj = value
+                        .to_object_value()
+                        .ok_or_else(|| ::juniper::FieldError::<#scalar>::from(
+                            ::std::format!("Expected input object, found: {}", value))
+                        )?;
+
+                    Ok(#ident {
+                        #( #fields )*
+                    })
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`ToInputValue`] trait for this
+    /// [GraphQL input object][0].
+    ///
+    /// [`ToInputValue`]: juniper::ToInputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_to_input_value_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let scalar = &self.scalar;
+
+        let generics = self.impl_generics(false);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        let fields = self.fields.iter().filter_map(|f| {
+            let ident = &f.ident;
+            let name = &f.name;
+
+            (!f.ignored).then(|| {
+                quote! {
+                    (#name, ::juniper::ToInputValue::to_input_value(&self.#ident))
+                }
+            })
+        });
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::ToInputValue<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
+                    ::juniper::InputValue::object(
+                        #[allow(deprecated)]
+                        ::std::array::IntoIter::new([#( #fields ),*])
+                            .collect()
+                    )
+                }
+            }
+        }
+    }
+
+    /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
+    /// [`WrappedType`] traits for this [GraphQL input object][0].
+    ///
+    /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes
+    /// [`BaseType`]: juniper::macros::reflect::BaseType
+    /// [`WrappedType`]: juniper::macros::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+    #[must_use]
+    fn impl_reflection_traits_tokens(&self) -> TokenStream {
+        let ident = &self.ident;
+        let name = &self.name;
+        let scalar = &self.scalar;
+
+        let generics = self.impl_generics(false);
+        let (impl_generics, _, where_clause) = generics.split_for_impl();
+        let (_, ty_generics, _) = self.generics.split_for_impl();
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                const NAME: ::juniper::macros::reflect::Type = #name;
+            }
+
+            impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                const NAMES: ::juniper::macros::reflect::Types =
+                    &[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
+            }
+
+            impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
+                for #ident#ty_generics
+                #where_clause
+            {
+                const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
+            }
+        }
+    }
+
+    /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and
+    /// similar) implementation of this struct.
+    ///
+    /// If `for_async` is `true`, then additional predicates are added to suit
+    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
+    ///
+    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
+    /// [`GraphQLType`]: juniper::GraphQLType
+    #[must_use]
+    fn impl_generics(&self, for_async: bool) -> syn::Generics {
+        let mut generics = self.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() {
+                // 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 ident = &self.ident;
+                let (_, ty_generics, _) = generics.split_for_impl();
+
+                quote! { for<#( #lifetimes ),*> #ident#ty_generics }
+            } else {
+                quote! { Self }
+            };
+            generics
+                .make_where_clause()
+                .predicates
+                .push(parse_quote! { #self_ty: Sync });
+
+            if scalar.is_generic() {
+                generics
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote! { #scalar: Send + Sync });
+            }
+        }
+
+        generics
+    }
+}
diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs
index 700fa3dc..18c3749d 100644
--- a/juniper_codegen/src/graphql_interface/mod.rs
+++ b/juniper_codegen/src/graphql_interface/mod.rs
@@ -66,7 +66,8 @@ struct Attr {
 
     /// Explicitly specified [description][2] of [GraphQL interface][1] type.
     ///
-    /// If [`None`], then Rust doc comment is used as [description][2], if any.
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs
index 0f3b05ac..88afd996 100644
--- a/juniper_codegen/src/graphql_object/mod.rs
+++ b/juniper_codegen/src/graphql_object/mod.rs
@@ -44,7 +44,8 @@ pub(crate) struct Attr {
 
     /// Explicitly specified [description][2] of this [GraphQL object][1] type.
     ///
-    /// If [`None`], then Rust doc comment is used as [description][2], if any.
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Objects
     /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs
index 21fb9cb4..c59b6732 100644
--- a/juniper_codegen/src/graphql_union/mod.rs
+++ b/juniper_codegen/src/graphql_union/mod.rs
@@ -47,7 +47,8 @@ struct Attr {
 
     /// Explicitly specified [description][2] of [GraphQL union][1] type.
     ///
-    /// If [`None`], then Rust doc comment is used as [description][2], if any.
+    /// If [`None`], then Rust doc comment will be used as the [description][2],
+    /// if any.
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Unions
     /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs
index 70c346ee..b0eeb053 100644
--- a/juniper_codegen/src/lib.rs
+++ b/juniper_codegen/src/lib.rs
@@ -100,10 +100,9 @@ macro_rules! try_merge_hashset {
     };
 }
 
-mod derive_input_object;
-
 mod common;
 mod graphql_enum;
+mod graphql_input_object;
 mod graphql_interface;
 mod graphql_object;
 mod graphql_scalar;
@@ -115,15 +114,115 @@ use proc_macro::TokenStream;
 use proc_macro_error::{proc_macro_error, ResultExt as _};
 use result::GraphQLScope;
 
+/// `#[derive(GraphQLInputObject)]` macro for deriving a
+/// [GraphQL input object][0] implementation for a Rust struct. Each
+/// non-ignored field type must itself be [GraphQL input object][0] or a
+/// [GraphQL scalar][2].
+///
+/// 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.
+///
+/// ```rust
+/// use juniper::GraphQLInputObject;
+///
+/// #[derive(GraphQLInputObject)]
+/// struct Point2D {
+///     x: f64,
+///     y: f64,
+/// }
+/// ```
+///
+/// # Custom name and description
+///
+/// The name of a [GraphQL input object][0] or its [fields][1] may be overridden
+/// with the `name` attribute's argument. By default, a type name or a struct
+/// field name is used in a `camelCase`.
+///
+/// The description of a [GraphQL input object][0] or its [fields][1] may be
+/// specified either with the `description`/`desc` attribute's argument, or with
+/// a regular Rust doc comment.
+///
+/// ```rust
+/// # use juniper::GraphQLInputObject;
+/// #
+/// #[derive(GraphQLInputObject)]
+/// #[graphql(
+///     // Rename the type for GraphQL by specifying the name here.
+///     name = "Point",
+///     // You may also specify a description here.
+///     // If present, doc comments will be ignored.
+///     desc = "A point is the simplest two-dimensional primitive.",
+/// )]
+/// struct Point2D {
+///     /// Abscissa value.
+///     x: f64,
+///
+///     #[graphql(name = "y", desc = "Ordinate value")]
+///     y_coord: f64,
+/// }
+/// ```
+///
+/// # Renaming policy
+///
+/// By default, all [GraphQL input object fields][1] are renamed in a
+/// `camelCase` manner (so a `y_coord` Rust struct field becomes a
+/// `yCoord` [value][1] in GraphQL schema, and so on). This complies with
+/// default GraphQL naming conventions as [demonstrated in spec][0].
+///
+/// However, if you need for some reason another naming convention, it's
+/// possible to do so by using the `rename_all` attribute's argument. At the
+/// moment, it supports the following policies only: `SCREAMING_SNAKE_CASE`,
+/// `camelCase`, `none` (disables any renaming).
+///
+/// ```rust
+/// # use juniper::GraphQLInputObject;
+/// #
+/// #[derive(GraphQLInputObject)]
+/// #[graphql(rename_all = "none")] // disables renaming
+/// struct Point2D {
+///     x: f64,
+///     y_coord: f64, // will be `y_coord` instead of `yCoord` in GraphQL schema
+/// }
+/// ```
+///
+/// # Ignoring fields
+///
+/// To omit exposing a Rust field in a GraphQL schema, use the `ignore`
+/// attribute's argument directly on that field. Ignored fields must implement
+/// [`Default`] or have the `default = <expression>` attribute's argument.
+///
+/// ```rust
+/// # use juniper::GraphQLInputObject;
+/// #
+/// enum System {
+///     Cartesian,
+/// }
+///
+/// #[derive(GraphQLInputObject)]
+/// struct Point2D {
+///     x: f64,
+///     y: f64,
+///     #[graphql(ignore)]
+///     shift: f64, // `Default::default()` impl is used.
+///     #[graphql(skip, default = System::Cartesian)]
+///     //              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+///     // This attribute is required, as we need to be to construct `Point2D`
+///     // from `{ x: 0.0, y: 0.0 }` GraphQL input.
+///     system: System,
+/// }
+/// ```
+///
+/// [`ScalarValue`]: juniper::ScalarValue
+/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
+/// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition
+/// [2]: https://spec.graphql.org/October2021#sec-Scalars
 #[proc_macro_error]
 #[proc_macro_derive(GraphQLInputObject, attributes(graphql))]
 pub fn derive_input_object(input: TokenStream) -> TokenStream {
-    let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
-    let gen = derive_input_object::impl_input_object(ast, GraphQLScope::DeriveInputObject);
-    match gen {
-        Ok(gen) => gen.into(),
-        Err(err) => proc_macro_error::abort!(err),
-    }
+    graphql_input_object::derive::expand(input.into())
+        .unwrap_or_abort()
+        .into()
 }
 
 /// `#[derive(GraphQLEnum)]` macro for deriving a [GraphQL enum][0]
diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs
index 5c979d69..9ec3793b 100644
--- a/juniper_codegen/src/result.rs
+++ b/juniper_codegen/src/result.rs
@@ -1,15 +1,16 @@
 //!
 
-use crate::util::duplicate::Duplicate;
+use std::fmt;
+
 use proc_macro2::Span;
 use proc_macro_error::{Diagnostic, Level};
-use std::fmt;
 
 /// URL of the GraphQL specification (June 2018 Edition).
 pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/";
 
 pub enum GraphQLScope {
     EnumDerive,
+    InputObjectDerive,
     InterfaceAttr,
     InterfaceDerive,
     ObjectAttr,
@@ -19,19 +20,18 @@ pub enum GraphQLScope {
     ScalarValueDerive,
     UnionAttr,
     UnionDerive,
-    DeriveInputObject,
 }
 
 impl GraphQLScope {
     pub fn spec_section(&self) -> &str {
         match self {
             Self::EnumDerive => "#sec-Enums",
+            Self::InputObjectDerive => "#sec-Input-Objects",
             Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces",
             Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
             Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars",
             Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars",
             Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
-            Self::DeriveInputObject => "#sec-Input-Objects",
         }
     }
 }
@@ -40,25 +40,17 @@ impl fmt::Display for GraphQLScope {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let name = match self {
             Self::EnumDerive => "enum",
+            Self::InputObjectDerive => "input object",
             Self::InterfaceAttr | Self::InterfaceDerive => "interface",
             Self::ObjectAttr | Self::ObjectDerive => "object",
             Self::ScalarAttr | Self::ScalarDerive => "scalar",
             Self::ScalarValueDerive => "built-in scalars",
             Self::UnionAttr | Self::UnionDerive => "union",
-            Self::DeriveInputObject => "input object",
         };
         write!(f, "GraphQL {}", name)
     }
 }
 
-#[allow(unused_variables)]
-#[derive(Debug)]
-pub enum UnsupportedAttribute {
-    Skip,
-    Interface,
-    Deprecation,
-}
-
 impl GraphQLScope {
     fn spec_link(&self) -> String {
         format!("{}{}", SPEC_URL, self.spec_section())
@@ -82,61 +74,6 @@ impl GraphQLScope {
         syn::Error::new(span, format!("{} {}", self, msg.as_ref()))
     }
 
-    pub fn unsupported_attribute(&self, attribute: Span, kind: UnsupportedAttribute) {
-        Diagnostic::spanned(
-            attribute,
-            Level::Error,
-            format!("attribute `{:?}` can not be used at the top level of {}", kind, self),
-        )
-        .note("The macro is known to Juniper. However, not all valid #[graphql] attributes are available for each macro".to_string())
-        .emit();
-    }
-
-    pub fn unsupported_attribute_within(&self, attribute: Span, kind: UnsupportedAttribute) {
-        Diagnostic::spanned(
-            attribute,
-            Level::Error,
-            format!("attribute `{:?}` can not be used inside of {}", kind, self),
-        )
-        .note("The macro is known to Juniper. However, not all valid #[graphql] attributes are available for each macro".to_string())
-        .emit();
-    }
-
-    pub fn not_empty(&self, container: Span) {
-        Diagnostic::spanned(
-            container,
-            Level::Error,
-            format!("{} expects at least one field", self),
-        )
-        .note(self.spec_link())
-        .emit();
-    }
-
-    pub fn duplicate<'a, T: syn::spanned::Spanned + 'a>(
-        &self,
-        duplicates: impl IntoIterator<Item = &'a Duplicate<T>>,
-    ) {
-        duplicates
-            .into_iter()
-            .for_each(|dup| {
-                dup.spanned[1..]
-                    .iter()
-                    .for_each(|spanned| {
-                        Diagnostic::spanned(
-                            spanned.span(),
-                            Level::Error,
-                            format!(
-                                "{} does not allow fields with the same name",
-                                self
-                            ),
-                        )
-                            .help(format!("There is at least one other field with the same name `{}`, possibly renamed via the #[graphql] attribute", dup.name))
-                            .note(self.spec_link())
-                            .emit();
-                    });
-            })
-    }
-
     pub fn no_double_underscore(&self, field: Span) {
         Diagnostic::spanned(
             field,
diff --git a/juniper_codegen/src/util/duplicate.rs b/juniper_codegen/src/util/duplicate.rs
deleted file mode 100644
index b056eb71..00000000
--- a/juniper_codegen/src/util/duplicate.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-//!
-
-use std::collections::HashMap;
-
-pub struct Duplicate<T> {
-    pub name: String,
-    pub spanned: Vec<T>,
-}
-
-impl<T> Duplicate<T> {
-    pub fn find_by_key<'a, F>(items: &'a [T], name: F) -> Option<Vec<Duplicate<&'a T>>>
-    where
-        T: 'a,
-        F: Fn(&'a T) -> &'a str,
-    {
-        let mut mapping: HashMap<&str, Vec<&T>> = HashMap::with_capacity(items.len());
-
-        for item in items {
-            if let Some(vals) = mapping.get_mut(name(item)) {
-                vals.push(item);
-            } else {
-                mapping.insert(name(item), vec![item]);
-            }
-        }
-
-        let duplicates = mapping
-            .into_iter()
-            .filter_map(|(k, v)| {
-                if v.len() != 1 {
-                    Some(Duplicate {
-                        name: k.to_string(),
-                        spanned: v,
-                    })
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-
-        if !duplicates.is_empty() {
-            Some(duplicates)
-        } else {
-            None
-        }
-    }
-}
diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs
index bda4bc6b..7edc1f44 100644
--- a/juniper_codegen/src/util/mod.rs
+++ b/juniper_codegen/src/util/mod.rs
@@ -1,25 +1,17 @@
 #![allow(clippy::single_match)]
 
-pub mod duplicate;
 pub mod span_container;
 
-use std::{collections::HashMap, convert::TryFrom, str::FromStr};
+use std::{convert::TryFrom, str::FromStr};
 
-use proc_macro2::{Span, TokenStream};
 use proc_macro_error::abort;
-use quote::{quote, quote_spanned};
 use span_container::SpanContainer;
 use syn::{
-    ext::IdentExt as _,
     parse::{Parse, ParseStream},
-    parse_quote,
-    punctuated::Punctuated,
     spanned::Spanned,
-    token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
+    Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
 };
 
-use crate::common::parse::ParseBufferExt as _;
-
 /// Compares a path to a one-segment string value,
 /// return true if equal.
 pub fn path_eq_single(path: &syn::Path, value: &str) -> bool {
@@ -31,12 +23,6 @@ pub struct DeprecationAttr {
     pub reason: Option<String>,
 }
 
-pub fn find_graphql_attr(attrs: &[Attribute]) -> Option<&Attribute> {
-    attrs
-        .iter()
-        .find(|attr| path_eq_single(&attr.path, "graphql"))
-}
-
 /// Filters given `attrs` to contain attributes only with the given `name`.
 pub fn filter_attrs<'a>(
     name: &'a str,
@@ -230,20 +216,6 @@ pub(crate) fn to_upper_snake_case(s: &str) -> String {
     upper
 }
 
-#[doc(hidden)]
-pub fn is_valid_name(field_name: &str) -> bool {
-    let mut chars = field_name.chars();
-
-    match chars.next() {
-        // first char can't be a digit
-        Some(c) if c.is_ascii_alphabetic() || c == '_' => (),
-        // can't be an empty string or any other character
-        _ => return false,
-    };
-
-    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
-}
-
 /// The different possible ways to change case of fields in a struct, or variants in an enum.
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum RenameRule {
@@ -292,674 +264,26 @@ impl Parse for RenameRule {
     }
 }
 
-#[derive(Default, Debug)]
-pub struct ObjectAttributes {
-    pub name: Option<SpanContainer<String>>,
-    pub description: Option<SpanContainer<String>>,
-    pub context: Option<SpanContainer<syn::Type>>,
-    pub scalar: Option<SpanContainer<syn::Type>>,
-    pub interfaces: Vec<SpanContainer<syn::Type>>,
-    pub no_async: Option<SpanContainer<()>>,
-    pub is_internal: bool,
-    pub rename: Option<RenameRule>,
-}
-
-impl Parse for ObjectAttributes {
-    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
-        let mut output = Self::default();
-
-        while !input.is_empty() {
-            let ident = input.parse_any_ident()?;
-            match ident.to_string().as_str() {
-                "name" => {
-                    input.parse::<token::Eq>()?;
-                    let val = input.parse::<syn::LitStr>()?;
-                    output.name = Some(SpanContainer::new(
-                        ident.span(),
-                        Some(val.span()),
-                        val.value(),
-                    ));
-                }
-                "description" => {
-                    input.parse::<token::Eq>()?;
-                    let val = input.parse::<syn::LitStr>()?;
-                    output.description = Some(SpanContainer::new(
-                        ident.span(),
-                        Some(val.span()),
-                        val.value(),
-                    ));
-                }
-                "context" | "Context" => {
-                    input.parse::<token::Eq>()?;
-                    // TODO: remove legacy support for string based Context.
-                    let ctx = if let Ok(val) = input.parse::<syn::LitStr>() {
-                        eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated");
-                        eprintln!("Use a normal type instead - example: 'Context = MyContextType'");
-                        syn::parse_str::<syn::Type>(&val.value())?
-                    } else {
-                        input.parse::<syn::Type>()?
-                    };
-                    output.context = Some(SpanContainer::new(ident.span(), Some(ctx.span()), ctx));
-                }
-                "scalar" | "Scalar" => {
-                    input.parse::<token::Eq>()?;
-                    let val = input.parse::<syn::Type>()?;
-                    output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val));
-                }
-                "impl" | "implements" | "interfaces" => {
-                    input.parse::<token::Eq>()?;
-                    output.interfaces = input.parse_maybe_wrapped_and_punctuated::<
-                        syn::Type, token::Bracket, token::Comma,
-                    >()?.into_iter()
-                        .map(|interface| {
-                            SpanContainer::new(ident.span(), Some(interface.span()), interface)
-                        })
-                        .collect();
-                }
-                // FIXME: make this unneccessary.
-                "noasync" => {
-                    output.no_async = Some(SpanContainer::new(ident.span(), None, ()));
-                }
-                "internal" => {
-                    output.is_internal = true;
-                }
-                "rename" => {
-                    input.parse::<token::Eq>()?;
-                    output.rename = Some(input.parse::<RenameRule>()?);
-                }
-                _ => {
-                    return Err(syn::Error::new(ident.span(), "unknown attribute"));
-                }
-            }
-            input.try_parse::<token::Comma>()?;
-        }
-
-        Ok(output)
-    }
-}
-
-impl ObjectAttributes {
-    pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::Result<Self> {
-        let attr_opt = find_graphql_attr(attrs);
-        if let Some(attr) = attr_opt {
-            // Need to unwrap  outer (), which are not present for proc macro attributes,
-            // but are present for regular ones.
-
-            let mut a: Self = attr.parse_args()?;
-            if a.description.is_none() {
-                a.description = get_doc_comment(attrs);
-            }
-            Ok(a)
-        } else {
-            Ok(Self {
-                description: get_doc_comment(attrs),
-                ..Self::default()
-            })
-        }
-    }
-}
-
-#[derive(Debug)]
-pub struct FieldAttributeArgument {
-    pub name: syn::Ident,
-    pub rename: Option<SpanContainer<syn::LitStr>>,
-    pub default: Option<syn::Expr>,
-    pub description: Option<syn::LitStr>,
-}
-
-impl Parse for FieldAttributeArgument {
-    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
-        let name = input.parse::<Ident>()?.unraw();
-
-        let mut arg = Self {
-            name,
-            rename: None,
-            default: None,
-            description: None,
-        };
-
-        let content;
-        syn::parenthesized!(content in input);
-        while !content.is_empty() {
-            let name = content.parse::<syn::Ident>()?;
-            content.parse::<token::Eq>()?;
-
-            match name.to_string().as_str() {
-                "name" => {
-                    let val: syn::LitStr = content.parse()?;
-                    arg.rename = Some(SpanContainer::new(name.span(), Some(val.span()), val));
-                }
-                "description" => {
-                    arg.description = Some(content.parse()?);
-                }
-                "default" => {
-                    arg.default = Some(content.parse()?);
-                }
-                _ => return Err(syn::Error::new(name.span(), "unknown attribute")),
-            }
-
-            // Discard trailing comma.
-            content.parse::<token::Comma>().ok();
-        }
-
-        Ok(arg)
-    }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-pub enum FieldAttributeParseMode {
-    Object,
-}
-
-enum FieldAttribute {
-    Name(SpanContainer<syn::LitStr>),
-    Description(SpanContainer<syn::LitStr>),
-    Deprecation(SpanContainer<DeprecationAttr>),
-    Skip(SpanContainer<syn::Ident>),
-    Arguments(HashMap<String, FieldAttributeArgument>),
-    Default(Box<SpanContainer<Option<syn::Expr>>>),
-}
-
-impl Parse for FieldAttribute {
-    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
-        let ident = input.parse::<syn::Ident>()?;
-
-        match ident.to_string().as_str() {
-            "name" => {
-                input.parse::<token::Eq>()?;
-                let lit = input.parse::<syn::LitStr>()?;
-                let raw = lit.value();
-                if !is_valid_name(&raw) {
-                    Err(syn::Error::new(lit.span(), "name consists of not allowed characters. (must match /^[_a-zA-Z][_a-zA-Z0-9]*$/)"))
-                } else {
-                    Ok(FieldAttribute::Name(SpanContainer::new(
-                        ident.span(),
-                        Some(lit.span()),
-                        lit,
-                    )))
-                }
-            }
-            "description" => {
-                input.parse::<token::Eq>()?;
-                let lit = input.parse::<syn::LitStr>()?;
-                Ok(FieldAttribute::Description(SpanContainer::new(
-                    ident.span(),
-                    Some(lit.span()),
-                    lit,
-                )))
-            }
-            "deprecated" | "deprecation" => {
-                let reason = if input.peek(token::Eq) {
-                    input.parse::<token::Eq>()?;
-                    Some(input.parse::<syn::LitStr>()?)
-                } else {
-                    None
-                };
-                Ok(FieldAttribute::Deprecation(SpanContainer::new(
-                    ident.span(),
-                    reason.as_ref().map(|val| val.span()),
-                    DeprecationAttr {
-                        reason: reason.map(|val| val.value()),
-                    },
-                )))
-            }
-            "skip" => Ok(FieldAttribute::Skip(SpanContainer::new(
-                ident.span(),
-                None,
-                ident,
-            ))),
-            "arguments" => {
-                let arg_content;
-                syn::parenthesized!(arg_content in input);
-                let args = Punctuated::<FieldAttributeArgument, token::Comma>::parse_terminated(
-                    &arg_content,
-                )?;
-                let map = args
-                    .into_iter()
-                    .map(|arg| (arg.name.to_string(), arg))
-                    .collect();
-                Ok(FieldAttribute::Arguments(map))
-            }
-            "default" => {
-                let default_expr = if input.peek(token::Eq) {
-                    input.parse::<token::Eq>()?;
-                    let lit = input.parse::<syn::LitStr>()?;
-                    let default_expr = lit.parse::<syn::Expr>()?;
-                    SpanContainer::new(ident.span(), Some(lit.span()), Some(default_expr))
-                } else {
-                    SpanContainer::new(ident.span(), None, None)
-                };
-
-                Ok(FieldAttribute::Default(Box::new(default_expr)))
-            }
-            _ => Err(syn::Error::new(ident.span(), "unknown attribute")),
-        }
-    }
-}
-
-#[derive(Default)]
-pub struct FieldAttributes {
-    pub name: Option<SpanContainer<String>>,
-    pub description: Option<SpanContainer<String>>,
-    pub deprecation: Option<SpanContainer<DeprecationAttr>>,
-    /// Only relevant for GraphQLObject derive.
-    pub skip: Option<SpanContainer<syn::Ident>>,
-    /// Only relevant for object macro.
-    pub arguments: HashMap<String, FieldAttributeArgument>,
-    /// Only relevant for object input objects.
-    pub default: Option<SpanContainer<Option<syn::Expr>>>,
-}
-
-impl Parse for FieldAttributes {
-    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
-        let items = Punctuated::<FieldAttribute, token::Comma>::parse_terminated(input)?;
-
-        let mut output = Self::default();
-
-        for item in items {
-            match item {
-                FieldAttribute::Name(name) => {
-                    output.name = Some(name.map(|val| val.value()));
-                }
-                FieldAttribute::Description(name) => {
-                    output.description = Some(name.map(|val| val.value()));
-                }
-                FieldAttribute::Deprecation(attr) => {
-                    output.deprecation = Some(attr);
-                }
-                FieldAttribute::Skip(ident) => {
-                    output.skip = Some(ident);
-                }
-                FieldAttribute::Arguments(args) => {
-                    output.arguments = args;
-                }
-                FieldAttribute::Default(expr) => {
-                    output.default = Some(*expr);
-                }
-            }
-        }
-
-        if !input.is_empty() {
-            Err(input.error("Unexpected input"))
-        } else {
-            Ok(output)
-        }
-    }
-}
-
-impl FieldAttributes {
-    pub fn from_attrs(
-        attrs: &[syn::Attribute],
-        _mode: FieldAttributeParseMode,
-    ) -> syn::Result<Self> {
-        let doc_comment = get_doc_comment(attrs);
-        let deprecation = get_deprecated(attrs);
-
-        let attr_opt = attrs.iter().find(|attr| attr.path.is_ident("graphql"));
-
-        let mut output = match attr_opt {
-            Some(attr) => attr.parse_args()?,
-            None => Self::default(),
-        };
-
-        // Check for regular doc comment.
-        if output.description.is_none() {
-            output.description = doc_comment;
-        }
-        if output.deprecation.is_none() {
-            output.deprecation = deprecation;
-        }
-
-        Ok(output)
-    }
-}
-
-#[derive(Debug)]
-pub struct GraphQLTypeDefinitionFieldArg {
-    pub name: String,
-    pub description: Option<String>,
-    pub default: Option<syn::Expr>,
-    pub _type: Box<syn::Type>,
-}
-
-#[derive(Debug)]
-pub struct GraphQLTypeDefinitionField {
-    pub name: String,
-    pub _type: syn::Type,
-    pub description: Option<String>,
-    pub deprecation: Option<DeprecationAttr>,
-    pub args: Vec<GraphQLTypeDefinitionFieldArg>,
-    pub resolver_code: TokenStream,
-    pub is_type_inferred: bool,
-    pub is_async: bool,
-    pub default: Option<TokenStream>,
-    pub span: Span,
-}
-
-impl syn::spanned::Spanned for GraphQLTypeDefinitionField {
-    fn span(&self) -> Span {
-        self.span
-    }
-}
-
-impl<'a> syn::spanned::Spanned for &'a GraphQLTypeDefinitionField {
-    fn span(&self) -> Span {
-        self.span
-    }
-}
-
-/// Definition of a graphql type based on information extracted
-/// by various macros.
-/// The definition can be rendered to Rust code.
-#[derive(Debug)]
-pub struct GraphQLTypeDefiniton {
-    pub name: String,
-    pub _type: syn::Type,
-    pub context: Option<syn::Type>,
-    pub scalar: Option<syn::Type>,
-    pub description: Option<String>,
-    pub fields: Vec<GraphQLTypeDefinitionField>,
-    pub generics: syn::Generics,
-    pub interfaces: Vec<syn::Type>,
-    // Due to syn parsing differences,
-    // when parsing an impl the type generics are included in the type
-    // directly, but in syn::DeriveInput, the type generics are
-    // in the generics field.
-    // This flag signifies if the type generics need to be
-    // included manually.
-    pub include_type_generics: bool,
-    // This flag indicates if the generated code should always be
-    // generic over the ScalarValue.
-    // If false, the scalar is only generic if a generic parameter
-    // is specified manually.
-    pub generic_scalar: bool,
-    // FIXME: make this redundant.
-    pub no_async: bool,
-}
-
-impl GraphQLTypeDefiniton {
-    #[allow(unused)]
-    fn has_async_field(&self) -> bool {
-        self.fields.iter().any(|field| field.is_async)
-    }
-
-    pub fn into_input_object_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 meta_fields = self
-            .fields
-            .iter()
-            .map(|field| {
-                // HACK: use a different interface for the GraphQLField?
-                let field_ty = &field._type;
-                let field_name = &field.name;
-
-                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 create_meta_field = match field.default {
-                    Some(ref def) => {
-                        quote! {
-                            registry.arg_with_default::<#field_ty>( #field_name, &#def, &())
-                        }
-                    }
-                    None => {
-                        quote! {
-                            registry.arg::<#field_ty>(#field_name, &())
-                        }
-                    }
-                };
-
-                quote!(
-                    {
-                        #create_meta_field
-                        #description
-                        #deprecation
-                    },
-                )
-            })
-            .collect::<Vec<_>>();
-
-        let from_inputs = self
-            .fields
-            .iter()
-            .map(|field| {
-                let field_ident = &field.resolver_code;
-                let field_name = &field.name;
-
-                // Build from_input clause.
-                let from_input_default = match field.default {
-                    Some(ref def) => {
-                        quote! {
-                            Some(&&::juniper::InputValue::Null) | None if true => #def,
-                        }
-                    }
-                    None => quote! {},
-                };
-
-                quote!(
-                    #field_ident: {
-                        match obj.get(#field_name) {
-                            #from_input_default
-                            Some(ref v) => {
-                                ::juniper::FromInputValue::<#scalar>::from_input_value(v)
-                                    .map_err(::juniper::IntoFieldError::into_field_error)?
-                            },
-                            None => {
-                                ::juniper::FromInputValue::<#scalar>::from_implicit_null()
-                                    .map_err(::juniper::IntoFieldError::into_field_error)?
-                            },
-                        }
-                    },
-                )
-            })
-            .collect::<Vec<_>>();
-
-        let to_inputs = self
-            .fields
-            .iter()
-            .map(|field| {
-                let field_name = &field.name;
-                let field_ident = &field.resolver_code;
-                // Build to_input clause.
-                quote!(
-                    (#field_name, self.#field_ident.to_input_value()),
-                )
-            })
-            .collect::<Vec<_>>();
-
-        let description = self
-            .description
-            .as_ref()
-            .map(|description| quote!( .description(#description) ));
-
-        // 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));
-
-            let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
-            // Insert ScalarValue constraint.
-            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 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 async_type = quote!(
-            impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #type_generics_tokens
-                #where_async
-            {}
-        );
-
-        let marks = self.fields.iter().map(|field| {
-            let field_ty = &field._type;
-            quote_spanned! { field_ty.span() =>
-                <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark();
-            }
-        });
-
-        let mut body = quote!(
-            impl#impl_generics ::juniper::marker::IsInputType<#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(_: &()) -> Option<&'static str> {
-                    Some(#name)
-                }
-
-                fn meta<'r>(
-                    _: &(),
-                    registry: &mut ::juniper::Registry<'r, #scalar>
-                ) -> ::juniper::meta::MetaType<'r, #scalar>
-                where #scalar: 'r
-                {
-                    let fields = &[
-                        #( #meta_fields )*
-                    ];
-                    registry.build_input_object_type::<#ty>(&(), fields)
-                    #description
-                    .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)
-                }
-            }
-
-            impl#impl_generics ::juniper::FromInputValue<#scalar> for #ty #type_generics_tokens
-                #where_clause
-            {
-                type Error = ::juniper::FieldError<#scalar>;
-
-                fn from_input_value(
-                    value: &::juniper::InputValue<#scalar>
-                ) -> Result<Self, Self::Error> {
-                    let obj = value
-                        .to_object_value()
-                        .ok_or_else(|| ::juniper::FieldError::<#scalar>::from(
-                            format!("Expected input object, found: {}", value))
-                        )?;
-                    Ok(#ty {
-                        #( #from_inputs )*
-                    })
-                }
-            }
-
-            impl#impl_generics ::juniper::ToInputValue<#scalar> for #ty #type_generics_tokens
-                #where_clause
-            {
-                fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
-                    ::juniper::InputValue::object(vec![
-                        #( #to_inputs )*
-                    ].into_iter().collect())
-                }
-            }
-
-            impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar>
-                for #ty #type_generics_tokens
-                #where_clause
-            {
-                const NAME: ::juniper::macros::reflect::Type = #name;
-            }
-
-            impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar>
-                for #ty #type_generics_tokens
-                #where_clause
-            {
-                const NAMES: ::juniper::macros::reflect::Types =
-                    &[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
-            }
-
-            impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
-                for #ty #type_generics_tokens
-                #where_clause
-            {
-                const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
-            }
-        );
-
-        if !self.no_async {
-            body.extend(async_type);
-        }
-
-        body
-    }
-}
-
 #[cfg(test)]
 mod test {
-    use super::*;
+    use proc_macro2::Span;
     use syn::{Ident, LitStr};
 
+    use super::*;
+
+    fn is_valid_name(field_name: &str) -> bool {
+        let mut chars = field_name.chars();
+
+        match chars.next() {
+            // first char can't be a digit
+            Some(c) if c.is_ascii_alphabetic() || c == '_' => (),
+            // can't be an empty string or any other character
+            _ => return false,
+        };
+
+        chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
+    }
+
     fn strs_to_strings(source: Vec<&str>) -> Vec<String> {
         source
             .iter()
diff --git a/tests/codegen/fail/input-object/derive_incompatible_field_type.rs b/tests/codegen/fail/input-object/derive_incompatible_field_type.rs
new file mode 100644
index 00000000..b3e26444
--- /dev/null
+++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.rs
@@ -0,0 +1,13 @@
+use juniper::{GraphQLInputObject, GraphQLObject};
+
+#[derive(GraphQLObject)]
+struct ObjectA {
+    test: String,
+}
+
+#[derive(GraphQLInputObject)]
+struct Object {
+    field: ObjectA,
+}
+
+fn main() {}
diff --git a/tests/codegen/fail/input-object/derive_incompatible_object.stderr b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr
similarity index 50%
rename from tests/codegen/fail/input-object/derive_incompatible_object.stderr
rename to tests/codegen/fail/input-object/derive_incompatible_field_type.stderr
index 6767eead..5ddabb55 100644
--- a/tests/codegen/fail/input-object/derive_incompatible_object.stderr
+++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr
@@ -1,8 +1,8 @@
 error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied
- --> fail/input-object/derive_incompatible_object.rs:8:12
+ --> fail/input-object/derive_incompatible_field_type.rs:8:10
   |
-8 |     field: ObjectA,
-  |            ^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA`
+8 | #[derive(GraphQLInputObject)]
+  |          ^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA`
   |
   = help: the following other types implement trait `IsInputType<S>`:
             <&T as IsInputType<S>>
@@ -14,12 +14,13 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied
             <Vec<T> as IsInputType<S>>
             <[T; N] as IsInputType<S>>
           and 13 others
+  = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
-    --> fail/input-object/derive_incompatible_object.rs:6:10
+    --> fail/input-object/derive_incompatible_field_type.rs:8:10
      |
-6    | #[derive(juniper::GraphQLInputObject)]
-     |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
+8    | #[derive(GraphQLInputObject)]
+     |          ^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
      |
      = help: the following other types implement trait `FromInputValue<S>`:
                <Arc<T> as FromInputValue<S>>
@@ -36,13 +37,13 @@ note: required by a bound in `Registry::<'r, S>::arg`
      |
      |         T: GraphQLType<S> + FromInputValue<S>,
      |                             ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg`
-     = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
+     = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
- --> fail/input-object/derive_incompatible_object.rs:6:10
+ --> fail/input-object/derive_incompatible_field_type.rs:8:10
   |
-6 | #[derive(juniper::GraphQLInputObject)]
-  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
+8 | #[derive(GraphQLInputObject)]
+  |          ^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA`
   |
   = help: the following other types implement trait `FromInputValue<S>`:
             <Arc<T> as FromInputValue<S>>
@@ -54,18 +55,22 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied
             <[T; N] as FromInputValue<S>>
             <bool as FromInputValue<__S>>
           and 10 others
-  = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
+  = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the current scope
- --> fail/input-object/derive_incompatible_object.rs:6:10
+error[E0277]: the trait bound `ObjectA: ToInputValue<_>` is not satisfied
+ --> fail/input-object/derive_incompatible_field_type.rs:8:10
   |
-2 | struct ObjectA {
-  |        ------- method `to_input_value` not found for this struct
-...
-6 | #[derive(juniper::GraphQLInputObject)]
-  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ObjectA`
+8 | #[derive(GraphQLInputObject)]
+  |          ^^^^^^^^^^^^^^^^^^ the trait `ToInputValue<_>` is not implemented for `ObjectA`
   |
-  = help: items from traits can only be used if the trait is implemented and in scope
-  = note: the following trait defines an item `to_input_value`, perhaps you need to implement it:
-          candidate #1: `ToInputValue`
-  = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
+  = help: the following other types implement trait `ToInputValue<S>`:
+            <&'a T as ToInputValue<S>>
+            <&'a [T] as ToInputValue<S>>
+            <&'a str as ToInputValue<S>>
+            <Arc<T> as ToInputValue<S>>
+            <Box<T> as ToInputValue<S>>
+            <ID as ToInputValue<__S>>
+            <Object as ToInputValue<__S>>
+            <TypeKind as ToInputValue<__S>>
+          and 14 others
+  = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/tests/codegen/fail/input-object/derive_incompatible_object.rs b/tests/codegen/fail/input-object/derive_incompatible_object.rs
deleted file mode 100644
index 98cdb65b..00000000
--- a/tests/codegen/fail/input-object/derive_incompatible_object.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-#[derive(juniper::GraphQLObject)]
-struct ObjectA {
-    test: String,
-}
-
-#[derive(juniper::GraphQLInputObject)]
-struct Object {
-    field: ObjectA,
-}
-
-fn main() {}
diff --git a/tests/codegen/fail/input-object/derive_no_fields.rs b/tests/codegen/fail/input-object/derive_no_fields.rs
index eedbe26e..2861298f 100644
--- a/tests/codegen/fail/input-object/derive_no_fields.rs
+++ b/tests/codegen/fail/input-object/derive_no_fields.rs
@@ -1,4 +1,6 @@
-#[derive(juniper::GraphQLInputObject)]
+use juniper::GraphQLInputObject;
+
+#[derive(GraphQLInputObject)]
 struct Object {}
 
 fn main() {}
diff --git a/tests/codegen/fail/input-object/derive_no_fields.stderr b/tests/codegen/fail/input-object/derive_no_fields.stderr
index 9362b428..4ab046c5 100644
--- a/tests/codegen/fail/input-object/derive_no_fields.stderr
+++ b/tests/codegen/fail/input-object/derive_no_fields.stderr
@@ -1,7 +1,5 @@
-error: GraphQL input object expects at least one field
- --> fail/input-object/derive_no_fields.rs:2:1
+error: GraphQL input object expected at least 1 non-ignored field
+ --> fail/input-object/derive_no_fields.rs:4:15
   |
-2 | struct Object {}
-  | ^^^^^^^^^^^^^^^^
-  |
-  = note: https://spec.graphql.org/June2018/#sec-Input-Objects
+4 | struct Object {}
+  |               ^^
diff --git a/tests/codegen/fail/input-object/derive_no_underscore.rs b/tests/codegen/fail/input-object/derive_no_underscore.rs
index 71ab5b9d..46b86a3c 100644
--- a/tests/codegen/fail/input-object/derive_no_underscore.rs
+++ b/tests/codegen/fail/input-object/derive_no_underscore.rs
@@ -1,4 +1,6 @@
-#[derive(juniper::GraphQLInputObject)]
+use juniper::GraphQLInputObject;
+
+#[derive(GraphQLInputObject)]
 struct Object {
     #[graphql(name = "__test")]
     test: String,
diff --git a/tests/codegen/fail/input-object/derive_no_underscore.stderr b/tests/codegen/fail/input-object/derive_no_underscore.stderr
index 86a6a9b2..b30b8aa9 100644
--- a/tests/codegen/fail/input-object/derive_no_underscore.stderr
+++ b/tests/codegen/fail/input-object/derive_no_underscore.stderr
@@ -1,7 +1,8 @@
 error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
- --> fail/input-object/derive_no_underscore.rs:3:15
+ --> fail/input-object/derive_no_underscore.rs:5:5
   |
-3 |     #[graphql(name = "__test")]
-  |               ^^^^
+5 | /     #[graphql(name = "__test")]
+6 | |     test: String,
+  | |________________^
   |
   = note: https://spec.graphql.org/June2018/#sec-Schema
diff --git a/tests/codegen/fail/input-object/derive_unique_name.rs b/tests/codegen/fail/input-object/derive_unique_name.rs
index ecaa8631..a0d9a5b2 100644
--- a/tests/codegen/fail/input-object/derive_unique_name.rs
+++ b/tests/codegen/fail/input-object/derive_unique_name.rs
@@ -1,4 +1,6 @@
-#[derive(juniper::GraphQLInputObject)]
+use juniper::GraphQLInputObject;
+
+#[derive(GraphQLInputObject)]
 struct Object {
     test: String,
     #[graphql(name = "test")]
diff --git a/tests/codegen/fail/input-object/derive_unique_name.stderr b/tests/codegen/fail/input-object/derive_unique_name.stderr
index 066fdbe6..20a72cfa 100644
--- a/tests/codegen/fail/input-object/derive_unique_name.stderr
+++ b/tests/codegen/fail/input-object/derive_unique_name.stderr
@@ -1,9 +1,10 @@
-error: GraphQL input object does not allow fields with the same name
- --> fail/input-object/derive_unique_name.rs:4:5
+error: GraphQL input object expected all fields to have unique names
+ --> fail/input-object/derive_unique_name.rs:4:15
   |
-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-Input-Objects
+4 |   struct Object {
+  |  _______________^
+5 | |     test: String,
+6 | |     #[graphql(name = "test")]
+7 | |     test2: String,
+8 | | }
+  | |_^
diff --git a/tests/integration/src/codegen/derive_input_object.rs b/tests/integration/src/codegen/derive_input_object.rs
deleted file mode 100644
index f85b042e..00000000
--- a/tests/integration/src/codegen/derive_input_object.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-use fnv::FnvHashMap;
-use juniper::{
-    graphql_input_value, marker, DefaultScalarValue, FieldError, FromInputValue,
-    GraphQLInputObject, GraphQLType, GraphQLValue, InputValue, Registry, ToInputValue,
-};
-
-#[derive(GraphQLInputObject, Debug, PartialEq)]
-#[graphql(
-    name = "MyInput",
-    description = "input descr",
-    scalar = DefaultScalarValue
-)]
-struct Input {
-    regular_field: String,
-    #[graphql(name = "haha", default = "33", description = "haha descr")]
-    c: i32,
-
-    #[graphql(default)]
-    other: Option<bool>,
-}
-
-#[derive(GraphQLInputObject, Debug, PartialEq)]
-#[graphql(rename = "none")]
-struct NoRenameInput {
-    regular_field: String,
-}
-
-/// Object comment.
-#[derive(GraphQLInputObject, Debug, PartialEq)]
-struct DocComment {
-    /// Field comment.
-    regular_field: bool,
-}
-
-/// Doc 1.\
-/// Doc 2.
-///
-/// Doc 4.
-#[derive(GraphQLInputObject, Debug, PartialEq)]
-struct MultiDocComment {
-    /// Field 1.
-    /// Field 2.
-    regular_field: bool,
-}
-
-/// This is not used as the description.
-#[derive(GraphQLInputObject, Debug, PartialEq)]
-#[graphql(description = "obj override")]
-struct OverrideDocComment {
-    /// This is not used as the description.
-    #[graphql(description = "field override")]
-    regular_field: bool,
-}
-
-#[derive(Debug, PartialEq)]
-struct Fake;
-
-impl<'a> marker::IsInputType<DefaultScalarValue> for &'a Fake {}
-
-impl<'a> FromInputValue for &'a Fake {
-    type Error = FieldError;
-
-    fn from_input_value(_v: &InputValue) -> Result<&'a Fake, Self::Error> {
-        Err("This is fake".into())
-    }
-}
-
-impl<'a> ToInputValue for &'a Fake {
-    fn to_input_value(&self) -> InputValue {
-        graphql_input_value!("this is fake")
-    }
-}
-
-impl<'a> GraphQLType<DefaultScalarValue> for &'a Fake {
-    fn name(_: &()) -> Option<&'static str> {
-        None
-    }
-    fn meta<'r>(_: &(), registry: &mut Registry<'r>) -> juniper::meta::MetaType<'r>
-    where
-        DefaultScalarValue: 'r,
-    {
-        let meta = registry.build_enum_type::<&'a Fake>(
-            &(),
-            &[juniper::meta::EnumValue {
-                name: "fake".to_string(),
-                description: None,
-                deprecation_status: juniper::meta::DeprecationStatus::Current,
-            }],
-        );
-        meta.into_meta()
-    }
-}
-
-impl<'a> GraphQLValue<DefaultScalarValue> for &'a Fake {
-    type Context = ();
-    type TypeInfo = ();
-
-    fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
-        <Self as GraphQLType>::name(info)
-    }
-}
-
-#[derive(GraphQLInputObject, Debug, PartialEq)]
-#[graphql(scalar = DefaultScalarValue)]
-struct WithLifetime<'a> {
-    regular_field: &'a Fake,
-}
-
-#[test]
-fn test_derived_input_object() {
-    assert_eq!(
-        <Input as GraphQLType<DefaultScalarValue>>::name(&()),
-        Some("MyInput")
-    );
-
-    // Validate meta info.
-    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"));
-
-    // Test default value injection.
-
-    let input_no_defaults = graphql_input_value!({
-        "regularField": "a",
-    });
-    let output_no_defaults = Input::from_input_value(&input_no_defaults).unwrap();
-    assert_eq!(
-        output_no_defaults,
-        Input {
-            regular_field: "a".into(),
-            c: 33,
-            other: None,
-        },
-    );
-
-    // Test with all values supplied.
-
-    let input: InputValue = ::serde_json::from_value(serde_json::json!({
-        "regularField": "a",
-        "haha": 55,
-        "other": true,
-    }))
-    .unwrap();
-
-    let output: Input = FromInputValue::from_input_value(&input).unwrap();
-    assert_eq!(
-        output,
-        Input {
-            regular_field: "a".into(),
-            c: 55,
-            other: Some(true),
-        },
-    );
-
-    // Test disable renaming
-
-    let input: InputValue = ::serde_json::from_value(serde_json::json!({
-        "regular_field": "hello",
-    }))
-    .unwrap();
-
-    let output: NoRenameInput = FromInputValue::from_input_value(&input).unwrap();
-    assert_eq!(
-        output,
-        NoRenameInput {
-            regular_field: "hello".into(),
-        },
-    );
-}
-
-#[test]
-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."));
-}
-
-#[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."));
-}
-
-#[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"));
-}
diff --git a/tests/integration/src/codegen/input_object_derive.rs b/tests/integration/src/codegen/input_object_derive.rs
new file mode 100644
index 00000000..085815b7
--- /dev/null
+++ b/tests/integration/src/codegen/input_object_derive.rs
@@ -0,0 +1,708 @@
+//! Tests for `#[derive(GraphQLInputObject)]` macro.
+
+use juniper::{execute, graphql_object, graphql_value, graphql_vars, GraphQLInputObject};
+
+use crate::util::schema;
+
+mod trivial {
+    use super::*;
+
+    #[derive(GraphQLInputObject)]
+    struct Point2D {
+        x: f64,
+        y: f64,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn x(point: Point2D) -> f64 {
+            point.x
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            x(point: { x: 10, y: 20 })
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"x": 10.0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_input_object() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"name": "Point2D"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_input_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                inputFields {
+                    name
+                    description
+                    type {
+                        ofType {
+                            name
+                        }
+                    }
+                    defaultValue
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"inputFields": [
+                    {
+                        "name": "x",
+                        "description": null,
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                    {
+                        "name": "y",
+                        "description": null,
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod default_value {
+    use super::*;
+
+    #[derive(GraphQLInputObject)]
+    struct Point2D {
+        #[graphql(default = 10.0)]
+        x: f64,
+        #[graphql(default = 10.0)]
+        y: f64,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn x(point: Point2D) -> f64 {
+            point.x
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            x(point: { y: 20 })
+            x2: x(point: { x: 20 })
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"x": 10.0, "x2": 20.0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_input_object() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_input_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                inputFields {
+                    name
+                    description
+                    type {
+                        ofType {
+                            name
+                        }
+                    }
+                    defaultValue
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"inputFields": [
+                    {
+                        "name": "x",
+                        "description": null,
+                        "type": {"ofType": null},
+                        "defaultValue": "10",
+                    },
+                    {
+                        "name": "y",
+                        "description": null,
+                        "type": {"ofType": null},
+                        "defaultValue": "10",
+                    },
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod ignored_field {
+    use super::*;
+
+    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+    enum System {
+        Cartesian,
+    }
+
+    #[derive(GraphQLInputObject)]
+    struct Point2D {
+        x: f64,
+        y: f64,
+        #[graphql(ignore)]
+        shift: f64,
+        #[graphql(skip, default = System::Cartesian)]
+        system: System,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn x(point: Point2D) -> f64 {
+            assert_eq!(point.shift, f64::default());
+            assert_eq!(point.system, System::Cartesian);
+            point.x
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            x(point: { x: 10, y: 20 })
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"x": 10.0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_input_object() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"name": "Point2D"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_no_description() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"description": null}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_input_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                inputFields {
+                    name
+                    description
+                    type {
+                        ofType {
+                            name
+                        }
+                    }
+                    defaultValue
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"inputFields": [
+                    {
+                        "name": "x",
+                        "description": null,
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                    {
+                        "name": "y",
+                        "description": null,
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_doc_comment {
+    use super::*;
+
+    /// Point in a Cartesian system.
+    #[derive(GraphQLInputObject)]
+    struct Point2D {
+        /// Abscissa value.
+        x: f64,
+
+        /// Ordinate value.
+        y_coord: f64,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn x(point: Point2D) -> f64 {
+            point.x
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            x(point: { x: 10, yCoord: 20 })
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"x": 10.0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_input_object() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"name": "Point2D"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "Point in a Cartesian system.",
+                }}),
+                vec![]
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_input_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                inputFields {
+                    name
+                    description
+                    type {
+                        ofType {
+                            name
+                        }
+                    }
+                    defaultValue
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"inputFields": [
+                    {
+                        "name": "x",
+                        "description": "Abscissa value.",
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                    {
+                        "name": "yCoord",
+                        "description": "Ordinate value.",
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod description_from_graphql_attr {
+    use super::*;
+
+    /// Ignored doc.
+    #[derive(GraphQLInputObject)]
+    #[graphql(name = "Point", desc = "Point in a Cartesian system.")]
+    struct Point2D {
+        /// Ignored doc.
+        #[graphql(name = "x", description = "Abscissa value.")]
+        x_coord: f64,
+
+        /// Ordinate value.
+        y: f64,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn x(point: Point2D) -> f64 {
+            point.x_coord
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            x(point: { x: 10, y: 20 })
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"x": 10.0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_input_object() {
+        const DOC: &str = r#"{
+            __type(name: "Point") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn uses_type_name() {
+        const DOC: &str = r#"{
+            __type(name: "Point") {
+                name
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"name": "Point"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_description() {
+        const DOC: &str = r#"{
+            __type(name: "Point") {
+                description
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {
+                    "description": "Point in a Cartesian system.",
+                }}),
+                vec![]
+            )),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_input_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Point") {
+                inputFields {
+                    name
+                    description
+                    type {
+                        ofType {
+                            name
+                        }
+                    }
+                    defaultValue
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"inputFields": [
+                    {
+                        "name": "x",
+                        "description": "Abscissa value.",
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                    {
+                        "name": "y",
+                        "description": "Ordinate value.",
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
+
+mod renamed_all_fields {
+    use super::*;
+
+    #[derive(GraphQLInputObject)]
+    #[graphql(rename_all = "none")]
+    struct Point2D {
+        x_coord: f64,
+        y: f64,
+    }
+
+    struct QueryRoot;
+
+    #[graphql_object]
+    impl QueryRoot {
+        fn x(point: Point2D) -> f64 {
+            point.x_coord
+        }
+    }
+
+    #[tokio::test]
+    async fn resolves() {
+        const DOC: &str = r#"{
+            x(point: { x_coord: 10, y: 20 })
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"x": 10.0}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn is_graphql_input_object() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                kind
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])),
+        );
+    }
+
+    #[tokio::test]
+    async fn has_input_fields() {
+        const DOC: &str = r#"{
+            __type(name: "Point2D") {
+                inputFields {
+                    name
+                    description
+                    type {
+                        ofType {
+                            name
+                        }
+                    }
+                    defaultValue
+                }
+            }
+        }"#;
+
+        let schema = schema(QueryRoot);
+
+        assert_eq!(
+            execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
+            Ok((
+                graphql_value!({"__type": {"inputFields": [
+                    {
+                        "name": "x_coord",
+                        "description": null,
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                    {
+                        "name": "y",
+                        "description": null,
+                        "type": {"ofType": {"name": "Float"}},
+                        "defaultValue": null,
+                    },
+                ]}}),
+                vec![],
+            )),
+        );
+    }
+}
diff --git a/tests/integration/src/codegen/mod.rs b/tests/integration/src/codegen/mod.rs
index 830f6e1a..fa65f453 100644
--- a/tests/integration/src/codegen/mod.rs
+++ b/tests/integration/src/codegen/mod.rs
@@ -1,6 +1,6 @@
-mod derive_input_object;
 mod derive_object_with_raw_idents;
 mod enum_derive;
+mod input_object_derive;
 mod interface_attr_struct;
 mod interface_attr_trait;
 mod interface_derive;