-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Specify nullness for nodes in the graphql.language package
#3899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR applies nullability annotations across the graphql.language package to clarify which fields and parameters can be null.
- Added @NullMarked and @nullable annotations to key classes and constructors.
- Updated Builder classes for several Value types to accept null parameters.
- Adjusted method signatures (e.g. isEqualTo) and field declarations to reflect nullability.
Reviewed Changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| VariableReference.java | Annotated the class and its Builder with nullability hints. |
| Value.java | Marked the interface with @NullMarked. |
| StringValue.java | Adjusted the field and getter for value to be @nullable. |
| ScalarValue.java | Applied @NullMarked to the interface. |
| ObjectValue.java | Annotated constructors and Builder methods with @nullable. |
| NullValue.java | Updated constructor and Builder to support null SourceLocation. |
| Node.java | Updated getSourceLocation and isEqualTo for nullability. |
| IntValue.java, FloatValue.java, EnumValue.java, BooleanValue.java, ArrayValue.java, AbstractNode.java | Applied similar nullability annotations to constructors, methods, and Builder classes. |
| private @Nullable SourceLocation sourceLocation; | ||
| private ImmutableList<Comment> comments = emptyList(); | ||
| private String name; | ||
| private @Nullable String name; |
Copilot
AI
May 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The VariableReference.Builder now allows a null name. Consider adding validation in the build() method to ensure that a non-null name is provided if that is an invariant in your domain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name in the above constructor and builder method are not nullable, did you meant to make this field nullable?
I think in practice, a VariableReference should always have a non-nullable name
| private SourceLocation sourceLocation; | ||
| private String name; | ||
| private @Nullable SourceLocation sourceLocation; | ||
| private @Nullable String name; |
Copilot
AI
May 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EnumValue.Builder permits a null name. It may be beneficial to enforce a non-null name during object construction if that is required by the API contract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, thanks for your patience while away for Easter.
In this builder, name is set to nullable, but the constructor above has name as non-nullable, and the builder method below has it as non-nullable. Did you mean for the Builder's name to be instead non-nullable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, nullable fields in the builder are expected.
By default, when a builder instance is created, all values are set to their defaults (null for reference fields) - thus, it's necessary to mark these fields @Nullable.
This initial Builder state represents an incomplete configuration, and the EnumValue should not be constructible in this state - calling the build() method should result in an IllegalStateException.
I planned to add something like this later:
public EnumValue build() {
if (name == null) {
throw new IllegalStateException("name must be set");
}
return new EnumValue(name, sourceLocation, comments, ignoredChars, additionalData);
}| private SourceLocation sourceLocation; | ||
| private BigInteger value; | ||
| private @Nullable SourceLocation sourceLocation; | ||
| private @Nullable BigInteger value; |
Copilot
AI
May 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IntValue.Builder now accepts a null value. Consider adding a check in the build() method to prevent creating an IntValue instance with a null value if that contradicts expected behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as for FloatValue, I will double check if a null value is in practice possible or not
| private SourceLocation sourceLocation; | ||
| private BigDecimal value; | ||
| private @Nullable SourceLocation sourceLocation; | ||
| private @Nullable BigDecimal value; |
Copilot
AI
May 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FloatValue.Builder now permits a null value. If a non-null value is expected, it would be prudent to validate this in the build() method to prevent possible misuse, as noted in the PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This builder should be consistent with the constructor for value
@andimarek @bbakerman: is it possible in practice for FloatValue to ever be constructed with a null? Or would such an object always be constructed as a NullValue?
|
Giving Copilot a try for fun, I'm curious what it says |
| private SourceLocation sourceLocation; | ||
| private String value; | ||
| private @Nullable SourceLocation sourceLocation; | ||
| private @Nullable String value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to above Value classes, I will double check if value can be nullable or if in practice it is represented by a NullValue
|
Coming back in here to say thanks for waiting and thanks for the PR. We have a new PR up to add ErrorProne & NullAway checks which I'd like to merge in before new JSpecify PRs |
| public ArrayValue deepCopy() { | ||
| return new ArrayValue(deepCopy(values), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); | ||
| List<Value> copiedValues = deepCopy(values); | ||
| return new ArrayValue(copiedValues != null ? copiedValues : emptyList(), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Want to call out this is a small change to not allow a null value. A list will be used instead. Without this change, there is a NullAway error
| return builder.build(); | ||
| } | ||
|
|
||
| @NullUnmarked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After giving a few classes the JSpecify treatment: we now generally annotate Builders with @NullUnmarked
| public ObjectValue deepCopy() { | ||
| return new ObjectValue(deepCopy(objectFields), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); | ||
| List<ObjectField> copiedFields = deepCopy(objectFields); | ||
| return new ObjectValue(copiedFields != null ? copiedFields : emptyList(), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to ArrayValue, want to call out this is a small change to not allow a null value. A list will be used instead. Without this change, there is a NullAway error
|
Hello after a few other PRs with JSpecify annotations, we settled on using We've also since added NullAway compile time checks which identified a very small extra change, to make the values of both ArrayValue and ObjectValue empty lists rather than null. Thanks for the PR! |
|
|
||
| @SuppressWarnings("unchecked") | ||
| protected <V extends Node> V deepCopy(V nullableObj) { | ||
| protected <V extends @Nullable Node> @Nullable V deepCopy(@Nullable V nullableObj) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this change @dondonz ... why should we allow null values here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first line we check whether the input is nullable if (nullableObj == null) { so I have allowed the input to be nullable
I agree it's strange in the context of Value like FloatValue.
Maybe there was another reason to allow this to be nullable in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we have two options here: either declare V as a type that also can be null or declare input and output as Nullable. We have done both here, which doesn't make sense I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repeating conversation here: I will change the method signature to become
protected <V extends Node> @Nullable V deepCopy(@Nullable V nullableObj)
This in English is meant to express that V extending Node is not nullable, but because the method will handle null inputs, it may still return a null
andimarek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be merged, but also could first fix the small comment I left.
|
|
||
| @SuppressWarnings("unchecked") | ||
| protected <V extends Node> V deepCopy(V nullableObj) { | ||
| protected <V extends @Nullable Node> @Nullable V deepCopy(@Nullable V nullableObj) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we have two options here: either declare V as a type that also can be null or declare input and output as Nullable. We have done both here, which doesn't make sense I think.
| @Override | ||
| public ArrayValue deepCopy() { | ||
| return new ArrayValue(deepCopy(values), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); | ||
| List<Value> copiedValues = assertNotNull(deepCopy(values)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an assert here to keep NullAway happy
In practice it is not possible to have null values. Have to put this here otherwise you'll get a NullAway error
| @Override | ||
| public ObjectValue deepCopy() { | ||
| return new ObjectValue(deepCopy(objectFields), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); | ||
| List<ObjectField> copiedFields = assertNotNull(deepCopy(objectFields)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this assert to keep NullAway happy, even though objectFields is not null
Related: #3878
I applied
@NullMarkedand@Nullableannotations to these classes:graphql.language.Node- partially annotated with@Nullablegraphql.language.AbstractNodegraphql.language.ArrayValuegraphql.language.BooleanValuegraphql.language.EnumValuegraphql.language.FloatValuegraphql.language.IntValuegraphql.language.NullValuegraphql.language.ObjectValuegraphql.language.ScalarValuegraphql.language.StringValue- I was not sure about the nullability ofvalue, for now I marked it@Nullable.graphql.language.Valuegraphql.language.VariableReferenceNotes
SourceLocation sourceLocation- can contain a null value in all placesPotential problems (will be pointed by #3852 )
EnumValue.Builder#build()can build anEnumValueobject with a nullnameFloatValue.Builder#build()can build anFloatValueobject with a nullvalueIntValue.Builder#build()can build anIntValueobject with a nullvalueVariableReference.Builder#build()can build anVariableReferenceobject with a nullname