Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/graphql/execution/ExecutionStepInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ public GraphQLOutputType getUnwrappedNonNullType() {
return (GraphQLOutputType) GraphQLTypeUtil.unwrapNonNull(this.type);
}

/**
* This returns the type which is unwrapped if it was {@link GraphQLNonNull} wrapped
* and then cast to the target type.
*
* @param <T> for two
*
* @return the graphql type in question
*/
public <T extends GraphQLOutputType> T getUnwrappedNonNullTypeAs() {
return GraphQLTypeUtil.unwrapNonNullAs(this.type);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes the consuming code easier to read


/**
* This returns the field definition that is in play when this type info was created or null
* if the type is a root query type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
public class ExecutionStepInfoFactory {

public ExecutionStepInfo newExecutionStepInfoForListElement(ExecutionStepInfo executionInfo, ResultPath indexedPath) {
GraphQLList fieldType = (GraphQLList) executionInfo.getUnwrappedNonNullType();
GraphQLList fieldType = executionInfo.getUnwrappedNonNullTypeAs();
GraphQLOutputType typeInList = (GraphQLOutputType) fieldType.getWrappedType();
return executionInfo.transform(typeInList, executionInfo, indexedPath);
}
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ protected Object resolveFieldWithInfo(ExecutionContext executionContext, Executi
@DuckTyped(shape = "CompletableFuture<FetchedValue|Object> | <FetchedValue|Object>")
protected Object fetchField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
MergedField field = parameters.getField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs();
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field.getSingleField());
return fetchField(fieldDef, executionContext, parameters);
}
Expand All @@ -408,7 +408,7 @@ private Object fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext exec
}

MergedField field = parameters.getField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs();

// if the DF (like PropertyDataFetcher) does not use the arguments or execution step info then dont build any

Expand Down Expand Up @@ -615,13 +615,13 @@ protected FieldValueInfo completeField(ExecutionContext executionContext,
executionContext.throwIfCancelled();

Field field = parameters.getField().getSingleField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs();
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field);
return completeField(fieldDef, executionContext, parameters, fetchedValue);
}

private FieldValueInfo completeField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters, Object fetchedValue) {
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs();
ExecutionStepInfo executionStepInfo = createExecutionStepInfo(executionContext, parameters, fieldDef, parentType);

Instrumentation instrumentation = executionContext.getInstrumentation();
Expand Down Expand Up @@ -1008,7 +1008,7 @@ private boolean incrementAndCheckMaxNodesExceeded(ExecutionContext executionCont
* @return a {@link GraphQLFieldDefinition}
*/
protected GraphQLFieldDefinition getFieldDef(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Field field) {
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs();
return getFieldDef(executionContext.getGraphQLSchema(), parentType, field);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ private ExecutionStrategyParameters firstFieldOfSubscriptionSelection(ExecutionC
private ExecutionStepInfo createSubscribedFieldStepInfo(ExecutionContext
executionContext, ExecutionStrategyParameters parameters) {
Field field = parameters.getField().getSingleField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs();
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field);
return createExecutionStepInfo(executionContext, parameters, fieldDef, parentType);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/graphql/schema/GraphQLNonNull.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public GraphQLNonNull(GraphQLType wrappedType) {
}

private void assertNonNullWrapping(GraphQLType wrappedType) {
assertTrue(!GraphQLTypeUtil.isNonNull(wrappedType),
"A non null type cannot wrap an existing non null type '%s'", GraphQLTypeUtil.simplePrint(wrappedType));
assertTrue(!GraphQLTypeUtil.isNonNull(wrappedType), () ->
"A non null type cannot wrap an existing non null type");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to remove the name of the type in this error message?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did - why?

because a static value that takes in no "enclosed" variables allocates no memory - the Supplier becomes a global singleton compiled into place.

but if we "enclose" a variable then it allocates the Supplier on every call.

So in the 0.0001% case where we have an error, yes the error message is better BUT we pay a memory price on the 99.9999% of the time where there is no error.

So yes its a less informative error message, but its also better at memory usage!

}

@Override
Expand Down
14 changes: 9 additions & 5 deletions src/main/java/graphql/schema/GraphQLTypeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -219,22 +219,26 @@ private static GraphQLType unwrapAllImpl(GraphQLType type) {


/**
* Unwraps all non nullable layers of the type until it reaches a type that is not {@link GraphQLNonNull}
* Unwraps a single non-nullable layer of the type if its present. Note there can
* only ever be one non-nullable wrapping of a type and this is enforced by {@link GraphQLNonNull}
*
* @param type the type to unwrap
*
* @return the underlying type that is not {@link GraphQLNonNull}
*/
public static GraphQLType unwrapNonNull(GraphQLType type) {
while (isNonNull(type)) {
type = unwrapOne(type);
// its illegal to have a type that is a non-null wrapping a non-null type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the input type is non-null list of non-null type? it should be handled here? IIRC I'm using this method somewhere in one project

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GraphqlNonNull has code to prevent it being double wrapped.

So

field( arg : String!!)

aka

nonNull(nonNull(GraphqlString)) is actually illegal and wont ever be allowed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that is good but what about a type like

ids: [ID!]!?

I suspect that the while loop was there to handle that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of [ID!]! it's not non-null wrapping a non-null, it would be non-null wrapping a list, wrapping a non-null

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok but I think this could be a breaking change for some projects (and this class is not marked as @internal IIRC)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping a non-null with another non-null is not allowed as per the specification: in the grammar it can only wrap a named or list type, not another non-null type. https://spec.graphql.org/draft/#sec-Type-References

Is there a situation where you would have had a non-null wrapping another non-null in your experience?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about the breaking change aspect and its not there. We already had assertion code to prevent ids : ID!! as a type so the code was overly defensive and I suspect written without the understanding that double non nulls is actually illegal.

So there is no breaking change here. Just a more efficient unwrapping of a single non null layer.

Copy link
Contributor

@dfa1 dfa1 Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem I see is that the previous code was unwrapping all the wrapper types (because unwrapOne/getWrappedType work for List too):
so, previously, a client of this method could call it with [ID!]! to get ID. Is there a unit test for this behavior?

Of course, I could be wrong ... I'm pretty sure my code could be broken by this change but I don't have my laptop with me now.

Copy link
Member Author

@bbakerman bbakerman Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem I see is that the previous code was unwrapping all the wrapper types (because unwrapOne/getWrappedType work for List too):

Its ok - itt didnt work like that. It only every unwrapped a non null . The code was

    public static GraphQLType unwrapNonNull(GraphQLType type) {
       while (isNonNull(type)) {
               type = unwrapOne(type);
    }


    public static GraphQLType unwrapOne(GraphQLType type) {
        if (isNonNull(type)) {
            return ((GraphQLNonNull) type).getWrappedType();
        } else if (isList(type)) {
            return ((GraphQLList) type).getWrappedType();
        }
        return type;
    }

So it would check if its non null and stop once it reached a type that is not non null. The reason its faster is that unwrapOne does another isNonNull instanceof check inside itself. So this avoids that.

Its not a major saving but its some saving. Thats why I said its a smidge faster

smidgen
/ˈsmɪdʒɪn/
nouninformal
noun: smidgen; plural noun: smidgens; noun: smidgin; plural noun: smidgins; noun: smidgeon; plural noun: smidgeons
a small amount of something.
"add a smidgen of cayenne"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good then, thanks for the explanation!

// and GraphQLNonNull has code that prevents it so we can just check once during the unwrapping
if (isNonNull(type)) {
// is cheaper doing this direct rather than calling #unwrapOne
type = ((GraphQLNonNull) type).getWrappedType();
}
return type;
}

/**
* Unwraps all non nullable layers of the type until it reaches a type that is not {@link GraphQLNonNull}
* and then cast to the target type.
* Unwraps a single non-nullable layer of the type if its present and then cast to the target type. Note there can
* only ever be one non-nullable wrapping of a type and this is enforced by {@link GraphQLNonNull}
*
* @param type the type to unwrap
* @param <T> for two
Expand Down
30 changes: 29 additions & 1 deletion src/test/groovy/graphql/schema/GraphQLTypeUtilTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import static graphql.Scalars.GraphQLString
import static graphql.schema.GraphQLList.list
import static graphql.schema.GraphQLNonNull.nonNull
import static graphql.schema.GraphQLObjectType.newObject
import static graphql.schema.GraphQLTypeReference.*
import static graphql.schema.GraphQLTypeReference.typeRef

class GraphQLTypeUtilTest extends Specification {

Expand Down Expand Up @@ -205,4 +205,32 @@ class GraphQLTypeUtilTest extends Specification {
then:
!GraphQLTypeUtil.isInput(type)
}

def "can unwrap non null-ness"() {

when:
def type = GraphQLTypeUtil.unwrapNonNull(nonNull(GraphQLString))

then:
(type as GraphQLNamedType).getName() == "String"

when:
type = GraphQLTypeUtil.unwrapNonNull(nonNull(list(GraphQLString)))

then:
type instanceof GraphQLList

when:
type = GraphQLTypeUtil.unwrapNonNull(list(GraphQLString))

then:
type instanceof GraphQLList

when:
type = GraphQLTypeUtil.unwrapNonNull(GraphQLString)

then:
(type as GraphQLNamedType).getName() == "String"

}
}