From b89f2493d5d61307cabeeda5ade2bcf03cda7e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Cybulski?= Date: Tue, 27 Oct 2026 14:11:42 +0100 Subject: [PATCH] fix(validation): keep argument defaults inside fragments Variable usages inside a fragment always got an undefined default value, so VariablesInAllowedPositionRule rejected a nullable variable passed through a fragment to a non-null argument with a default, e.g. `$intVar: Int` used as `nonNullFieldWithDefault(arg: $intVar)` inside a fragment. GraphQL-core 3.2 accepted such queries. Use the argument default for every variable usage, as graphql-js does since graphql/graphql-js#4865. Co-Authored-By: Claude Opus 5.5 --- src/graphql/validation/validation_context.py | 28 ++++++----------- .../test_variables_in_allowed_position.py | 31 ++++++++++++++++++- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/graphql/validation/validation_context.py b/src/graphql/validation/validation_context.py index d39f94aa..175403ef 100644 --- a/src/graphql/validation/validation_context.py +++ b/src/graphql/validation/validation_context.py @@ -21,7 +21,6 @@ VisitorAction, visit, ) -from ..pyutils import Undefined from ..utilities import TypeInfo, TypeInfoVisitor if TYPE_CHECKING: @@ -85,33 +84,24 @@ def enter_variable_definition(self, *_args: Any) -> VisitorAction: def enter_variable(self, node: VariableNode, *_args: Any) -> VisitorAction: type_info = self._type_info fragment_definition = self._fragment_definition + fragment_variable_definition = None if fragment_definition: fragment_signature = type_info.get_fragment_signature_by_name()( fragment_definition.name.value ) - fragment_variable_definition = ( - fragment_signature.variable_definitions.get(node.name.value) - if fragment_signature - else None - ) - # Fragment variables have a variable default but no location default, - # which is what this default value represents. - usage = VariableUsage( - node, - type_info.get_input_type(), - type_info.get_parent_input_type(), - Undefined, - fragment_variable_definition, - ) - else: - usage = VariableUsage( + if fragment_signature: + fragment_variable_definition = ( + fragment_signature.variable_definitions.get(node.name.value) + ) + self._append_usage( + VariableUsage( node, type_info.get_input_type(), type_info.get_parent_input_type(), type_info.get_default_value(), - None, + fragment_variable_definition, ) - self._append_usage(usage) + ) return None diff --git a/tests/validation/test_variables_in_allowed_position.py b/tests/validation/test_variables_in_allowed_position.py index 41e37f05..56683dd6 100644 --- a/tests/validation/test_variables_in_allowed_position.py +++ b/tests/validation/test_variables_in_allowed_position.py @@ -374,6 +374,20 @@ def forbids_one_nullable_variable(): ) def describe_fragment_arguments_are_validated(): + def int_to_non_null_int_when_field_argument_provides_default_value(): + assert_valid( + """ + query Query { + complicatedArgs { + ...A + } + } + fragment A($intVar: Int) on ComplicatedArgs { + nonNullFieldWithDefault(arg: $intVar) + } + """ + ) + def validates_fragment_variables_defined_before_the_operation(): assert_errors( """ @@ -505,7 +519,22 @@ def int_to_non_null_int_when_optional_arg_provides_default_value(): """ query Query($intVar: Int) { complicatedArgs { - nonNullFieldWithDefault(nonNullIntArg: $intVar) + nonNullFieldWithDefault(arg: $intVar) + } + } + """ + ) + + def int_to_non_null_int_when_argument_default_is_inside_fragment(): + assert_valid( + """ + fragment defaultArgFrag on ComplicatedArgs { + nonNullFieldWithDefault(arg: $intVar) + } + + query Query($intVar: Int) { + complicatedArgs { + ...defaultArgFrag } } """