From e265dd65b6bbf02af3cddad0a81b576e16e89a0f Mon Sep 17 00:00:00 2001 From: Joey Tepperman Date: Fri, 8 May 2026 16:17:16 -0400 Subject: [PATCH 1/2] Fix nullability of GraphQL scalar types when passed as required arguments Currently, the generated rbis for a GraphQL mutation that contains an argument that uses a scalar type will always treat the argument as nullable, even when it is required. This PR fixes it so that required arguments are treated as non-nullable. --- .../dsl/helpers/graphql_type_helper.rb | 2 +- .../dsl/compilers/graphql_mutation_spec.rb | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/tapioca/dsl/helpers/graphql_type_helper.rb b/lib/tapioca/dsl/helpers/graphql_type_helper.rb index e38bf818c..7ffd40860 100644 --- a/lib/tapioca/dsl/helpers/graphql_type_helper.rb +++ b/lib/tapioca/dsl/helpers/graphql_type_helper.rb @@ -80,7 +80,7 @@ def type_for(type, ignore_nilable_wrapper: false, prepare_method: nil) signature = Runtime::Reflection.signature_of(method) return_type = signature&.return_type - valid_return_type?(return_type) ? return_type.to_s : "T.untyped" + valid_return_type?(return_type) ? RBIHelper.as_non_nilable_type(return_type.to_s) : "T.untyped" when GraphQL::Schema::InputObject.singleton_class type_for_constant(unwrapped_type) when Module diff --git a/spec/tapioca/dsl/compilers/graphql_mutation_spec.rb b/spec/tapioca/dsl/compilers/graphql_mutation_spec.rb index 1311f6ae2..4850770d1 100644 --- a/spec/tapioca/dsl/compilers/graphql_mutation_spec.rb +++ b/spec/tapioca/dsl/compilers/graphql_mutation_spec.rb @@ -361,6 +361,45 @@ def resolve(loaded_argument:, loaded_arguments:, custom_name:, optional_loaded_a assert_equal(expected, rbi_for(:CreateComment)) end + it "generates correct RBI for custom scalars whose coerce_input returns a nilable type" do + add_ruby_file("create_comment.rb", <<~RUBY) + class CustomScalar; end + + class NilableScalarType < GraphQL::Schema::Scalar + class << self + extend T::Sig + + sig { params(value: T.untyped, context: GraphQL::Query::Context).returns(T.nilable(CustomScalar)) } + def coerce_input(value, context) + return nil if value.nil? + CustomScalar.new + end + end + end + + class CreateComment < GraphQL::Schema::Mutation + argument :required_scalar, NilableScalarType, required: true + argument :required_scalar_array, [NilableScalarType], required: true + argument :optional_scalar, NilableScalarType, required: false + + def resolve(required_scalar:, required_scalar_array:, optional_scalar: nil) + # ... + end + end + RUBY + + expected = <<~RBI + # typed: strong + + class CreateComment + sig { params(required_scalar: ::CustomScalar, required_scalar_array: T::Array[::CustomScalar], optional_scalar: T.nilable(::CustomScalar)).returns(T.untyped) } + def resolve(required_scalar:, required_scalar_array:, optional_scalar: T.unsafe(nil)); end + end + RBI + + assert_equal(expected, rbi_for(:CreateComment)) + end + it "generates correct RBI for custom scalars with return types" do add_ruby_file("create_comment.rb", <<~RUBY) class CustomScalar; end From 57bdcabaad268cbfe085c13a84fa0d93d3b5979b Mon Sep 17 00:00:00 2001 From: Joey Tepperman Date: Wed, 13 May 2026 14:39:30 -0400 Subject: [PATCH 2/2] Add comment explaining non_nilable type wrapping --- lib/tapioca/dsl/helpers/graphql_type_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tapioca/dsl/helpers/graphql_type_helper.rb b/lib/tapioca/dsl/helpers/graphql_type_helper.rb index 7ffd40860..36e554855 100644 --- a/lib/tapioca/dsl/helpers/graphql_type_helper.rb +++ b/lib/tapioca/dsl/helpers/graphql_type_helper.rb @@ -80,6 +80,8 @@ def type_for(type, ignore_nilable_wrapper: false, prepare_method: nil) signature = Runtime::Reflection.signature_of(method) return_type = signature&.return_type + # Wrap as non-nilable for required arguments. `coerce_input` supports both + # required and optional; optional arguments are re-wrapped below based on `type.non_null?` valid_return_type?(return_type) ? RBIHelper.as_non_nilable_type(return_type.to_s) : "T.untyped" when GraphQL::Schema::InputObject.singleton_class type_for_constant(unwrapped_type)