From 293b21b9146f9e22bc10dc6396fd90d40e3f965d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 16:42:20 +0000 Subject: [PATCH 1/2] Add failing test for overlapping fields null type regression (072165b) The requireSameOutputTypeShape() null check in OperationValidator was incorrectly simplified in commit 072165b. When a FieldAndType set contains both null-typed entries (from unresolvable parent types) and non-null entries (from interface inline fragments), the refactored code reports a spurious FieldsConflict error instead of skipping the comparison. This breaks the benchmarkDeepAbstractConcrete JMH benchmark. https://claude.ai/code/session_01XYg9Dqneb941aStgKaZ1Em --- .../OverlappingFieldsCanBeMergedTest.groovy | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/groovy/graphql/validation/OverlappingFieldsCanBeMergedTest.groovy b/src/test/groovy/graphql/validation/OverlappingFieldsCanBeMergedTest.groovy index 779ac756ea..551ffb028a 100644 --- a/src/test/groovy/graphql/validation/OverlappingFieldsCanBeMergedTest.groovy +++ b/src/test/groovy/graphql/validation/OverlappingFieldsCanBeMergedTest.groovy @@ -6,6 +6,7 @@ import graphql.language.SourceLocation import graphql.parser.Parser import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLSchema +import graphql.schema.idl.SchemaGenerator import graphql.validation.LanguageTraversal import graphql.validation.OperationValidationRule import graphql.validation.OperationValidator @@ -967,6 +968,49 @@ class OverlappingFieldsCanBeMergedTest extends Specification { errorCollector.getErrors().size() == 0 } + def "mixed null and non-null field types from abstract and concrete inline fragments should not conflict"() { + given: + // Reproduces the benchmarkDeepAbstractConcrete scenario from OverlappingFieldValidationPerformance. + // The fragment spreads on interfaces (Abstract1, Abstract2) resolve "field" to type Abstract + // (because interfaces implement GraphQLFieldsContainer), but the fragment itself is on "Whatever" + // which doesn't exist, so the outer "field" selected directly from the fragment has null type. + // When mergeSubSelections collects sub-fields, the inner "field" selections end up as a set + // containing both null-typed entries (from the unresolvable parent) and Abstract-typed entries + // (from the interface inline fragments). The old code treated null as "unknown, skip comparison"; + // commit 072165b regressed this by treating null typeB as an unconditional conflict. + def schema = SchemaGenerator.createdMockedSchema(''' + type Query { viewer: Viewer } + interface Abstract { field: Abstract leaf: Int } + interface Abstract1 { field: Abstract leaf: Int } + interface Abstract2 { field: Abstract leaf: Int } + type Concrete1 implements Abstract1 { field: Abstract leaf: Int } + type Concrete2 implements Abstract2 { field: Abstract leaf: Int } + type Viewer { xingId: XingId } + type XingId { firstName: String! lastName: String! } + ''') + // Nesting depth > 1 is required: the outer "field" on Query is unresolvable (null type), + // and the fragment's inline fragments resolve "field" on known interfaces (non-null type). + // After mergeSubSelections, the inner "field" set contains both null and non-null typed entries. + def query = ''' + fragment multiply on Whatever { + field { + ... on Abstract1 { field { leaf } } + ... on Abstract2 { field { leaf } } + ... on Concrete1 { field { leaf } } + ... on Concrete2 { field { leaf } } + } + } + query DeepAbstractConcrete { + field { ...multiply field { ...multiply } } + } + ''' + when: + traverse(query, schema) + + then: + errorCollector.getErrors().isEmpty() + } + def "overlapping fields on lower level"() { given: def schema = schema(''' From e27544af3e550db34ea2dc6e9d007ae02af6029a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 17:03:53 +0000 Subject: [PATCH 2/2] Fix null type handling regression in requireSameOutputTypeShape Restore the original null-handling logic that was incorrectly simplified in 072165b. When both typeA and typeB are null (e.g., from fragments on unresolvable types), they should be treated as compatible rather than producing a spurious FieldsConflict error. https://claude.ai/code/session_01XYg9Dqneb941aStgKaZ1Em --- src/main/java/graphql/validation/OperationValidator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/validation/OperationValidator.java b/src/main/java/graphql/validation/OperationValidator.java index fea8df24aa..d961486116 100644 --- a/src/main/java/graphql/validation/OperationValidator.java +++ b/src/main/java/graphql/validation/OperationValidator.java @@ -1241,8 +1241,11 @@ private boolean sameArguments(List arguments1, @Nullable List