diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 92f222cb3..80908c6b5 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -43,7 +43,7 @@ Token :: - FloatValue - StringValue -Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | } +Punctuator :: one of ! ? $ & ( ) ... : = @ [ ] { | } Name :: diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index f5e245ab3..3bbea9c17 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1224,6 +1224,8 @@ that variable, that operation is invalid (see ## Type References +### In traditional nullability mode + Type : - NamedType @@ -1239,10 +1241,49 @@ NonNullType : - NamedType ! - ListType ! +**Semantics** + +Type : Name + +- Let {name} be the string value of {Name} +- Let {type} be the type defined in the Schema named {name} +- {type} must not be {null} +- Return {type} + +Type : [ Type ] + +- Let {itemType} be the result of evaluating {Type} +- Let {type} be a List type where {itemType} is the contained type. +- Return {type} + +Type : Type ! + +- Let {nullableType} be the result of evaluating {Type} +- Let {type} be a Non-Null type where {nullableType} is the contained type. +- Return {type} + +### In semantic nullability mode + +Type : + +- NamedType +- ListType +- NonNullType +- SemanticNonNullType + +NamedType : Name ? + +ListType : [ Type ] + +NonNullType : + +- Name ! +- ListType ! + SemanticNonNullType : -- ! NamedType -- ! ListType +- Name [lookahead != `?`] [lookahead != `!`] +- ListType [lookahead != `?`] [lookahead != `!`] GraphQL describes the types of data expected by arguments and variables. Input types may be lists of another input type, or a non-null variant of any other @@ -1257,6 +1298,17 @@ Type : Name - {type} must not be {null} - Return {type} +Type : Type ? + +- Let {nullableType} be the result of evaluating {Type} +- Return {nullableType} + +Type : Type [lookahead != `?`] [lookahead != `!`] + +- Let {nullableType} be the result of evaluating {Type} +- Let {type} be a Semantically-Non-Null type where {nullableType} is the contained type. +- Return {type} + Type : [ Type ] - Let {itemType} be the result of evaluating {Type} diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 5b7248d2b..dc6155e94 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1905,18 +1905,17 @@ Following are examples of result coercion with various types and values: The GraphQL Semantic-Non-Null type is an alternative to the GraphQL Non-Null type to disallow null unless accompanied by a field error. This type wraps an underlying type, and this type acts identically to that wrapped type, with the -exception that {null} will result in a field error being raised. A leading -exclamation mark is used to denote a field that uses a Semantic-Non-Null type -like this: `name: !String`. - +exception that {null} will result in a field error being raised. Semantic-Non-Null +types are only valid in a document that also contains a `@SemanticNullability` +document-level directive. In such a document, Semantic-Non-Null fields have no +adornment, much like nullable fields in documents without that directive. +For example: `name: String`. + +In docuements with a `@SemanticNullability` directive, input types are unaltered in +terms of syntax. Unadorned types still represent nullable input types. Semantic-Non-Null types are only valid for use as an _output type_; they must not be used as an _input type_. -**Nullable vs. Optional** - -Fields that return Semantic-Non-Null types will never return the value {null} if -queried _unless_ an error has been logged for that field. - **Result Coercion** To coerce the result of a Semantic-Non-Null type, the coercion of the wrapped @@ -1947,8 +1946,8 @@ complex types. The rules for result coercion of Lists and Semantic-Non-Null types apply in a recursive fashion. For example if the inner item type of a List is Semantic-Non-Null (e.g. `[!T]`), -then that List may not contain any {null} items unless associated field errors -were raised. However if the inner type of a Semantic-Non-Null is a List (e.g. +then if that list contains any nulls without associated errors, then field errors will be raised. +However if the inner type of a Semantic-Non-Null is a List (e.g. `![T]`), then {null} is not accepted without an accompanying field error being raised, however an empty list is accepted. @@ -1956,26 +1955,26 @@ Following are examples of result coercion with various types and values: | Expected Type | Internal Value | Coerced Result | | ------------- | --------------- | ------------------------------------------- | -| `![Int]` | `[1, 2, 3]` | `[1, 2, 3]` | -| `![Int]` | `null` | `null` (With logged coercion error) | -| `![Int]` | `[1, 2, null]` | `[1, 2, null]` | -| `![Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | -| `![Int!]` | `[1, 2, 3]` | `[1, 2, 3]` | -| `![Int!]` | `null` | `null` (With logged coercion error) | -| `![Int!]` | `[1, 2, null]` | `null` (With logged coercion error) | -| `![Int!]` | `[1, 2, Error]` | `null` (With logged error) | -| `[!Int]` | `[1, 2, 3]` | `[1, 2, 3]` | -| `[!Int]` | `null` | `null` | -| `[!Int]` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) | -| `[!Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | -| `[!Int]!` | `[1, 2, 3]` | `[1, 2, 3]` | -| `[!Int]!` | `null` | Error: Value cannot be null | -| `[!Int]!` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) | -| `[!Int]!` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | -| `![!Int]` | `[1, 2, 3]` | `[1, 2, 3]` | -| `![!Int]` | `null` | `null` (With logged coercion error) | -| `![!Int]` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) | -| `![!Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | +| `[Int]` | `[1, 2, 3]` | `[1, 2, 3]` | +| `[Int]` | `null` | `null` (With logged coercion error) | +| `[Int]` | `[1, 2, null]` | `[1, 2, null]` | +| `[Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | +| `[Int!]` | `[1, 2, 3]` | `[1, 2, 3]` | +| `[Int!]` | `null` | `null` (With logged coercion error) | +| `[Int!]` | `[1, 2, null]` | `null` (With logged coercion error) | +| `[Int!]` | `[1, 2, Error]` | `null` (With logged error) | +| `[Int]` | `[1, 2, 3]` | `[1, 2, 3]` | +| `[Int]` | `null` | `null` | +| `[Int]` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) | +| `[Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | +| `[Int]!` | `[1, 2, 3]` | `[1, 2, 3]` | +| `[Int]!` | `null` | Error: Value cannot be null | +| `[Int]!` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) | +| `[Int]!` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | +| `[Int]` | `[1, 2, 3]` | `[1, 2, 3]` | +| `[Int]` | `null` | `null` (With logged coercion error) | +| `[Int]` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) | +| `[Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) | ## Directives @@ -2016,6 +2015,7 @@ TypeSystemDirectiveLocation : one of - `ENUM_VALUE` - `INPUT_OBJECT` - `INPUT_FIELD_DEFINITION` +- `DOCUMENT` A GraphQL schema describes directives which are used to annotate various parts of a GraphQL document as an indicator that they should be evaluated differently @@ -2025,7 +2025,8 @@ by a validator, executor, or client tool such as a code generator. :: A _built-in directive_ is any directive defined within this specification. -GraphQL implementations should provide the `@skip` and `@include` directives. +GraphQL implementations should provide the `@skip`, `@include`, and +`@semanticNullability` directives. GraphQL implementations that support the type system definition language must provide the `@deprecated` directive if representing deprecated portions of the @@ -2180,6 +2181,20 @@ the `@include` condition is true. Stated conversely, the field or fragment must _not_ be queried if either the `@skip` condition is true _or_ the `@include` condition is false. +### @semanticNullability + +```graphql +directive @semanticNullability on DOCUMENT +``` + +The `@semanticNullability` _built-in directive_ may be provided for documents, +and causes GraphQL to interpret nullability in that document based on the rules +for Semantic Nullability. In that document an unadorned named type will be treated +as if it is wrapped with the Semantic-Non-Null type. Any fields adorned with a question +mark ie `name: Type?` will be treated as a named nullable type. + +An unadorned input type will be treated as a nullable type. + ### @deprecated ```graphql diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 6d150a52d..d46d5e5f7 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -165,11 +165,16 @@ enum __TypeKind { SEMANTIC_NON_NULL } +enum __NullabilityMode { + TRADITIONAL + FULL +} + type __Field { name: String! description: String args(includeDeprecated: Boolean = false): [__InputValue!]! - type(includeSemanticNonNull: Boolean! = false): __Type! + type(nullabilityMode: __NullabilityMode! = __NullabilityMode.TRADITIONAL): __Type! isDeprecated: Boolean! deprecationReason: String } @@ -416,8 +421,9 @@ Fields\: GraphQL types are nullable. The value {null} is a valid response for field type. A Semantic-Non-Null type is a type modifier: it wraps another _output type_ -instance in the `ofType` field. Semantic-Non-Null types do not allow {null} as a -response _unless_ an associated _field error_ has been raised. +instance in the `ofType` field. Semantic-Non-Null types allow {null} as a +response, however if an associated _field error_ has not already been raised, +one will be produced. The modified type in the `ofType` field may itself be a modified List type, allowing the representation of Semantic-Non-Null of Lists. However it must not @@ -445,8 +451,8 @@ Fields\: {true}, deprecated arguments are also returned. - `type` must return a `__Type` that represents the type of value returned by this field. - - Accepts the argument `includeSemanticNonNull` which defaults to {false}. If - {false}, let {fieldType} be the type of value returned by this field and + - Accepts the argument `nullabilityMode` which defaults to {__NullabilityMode.TRADITIONAL}. If + {__NullabilityMode.TRADITIONAL}, let {fieldType} be the type of value returned by this field and instead return a `__Type` that represents {RecursivelyStripSemanticNonNullTypes(fieldType)}. - `isDeprecated` returns {true} if this field should no longer be used, diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 4e7bd0571..60972908c 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -784,8 +784,8 @@ response. If the result of resolving a field is {null} (either because the function to resolve the field returned {null} or because a field error was raised), and that -field is of a `Non-Null` type, then a field error is raised. The error must be -added to the {"errors"} list in the response. +field is of a `Non-Null` or `Semantically-Non-Null` type, then a field error is +raised. The error must be added to the {"errors"} list in the response. If the field returns {null} because of a field error which has already been added to the {"errors"} list in the response, the {"errors"} list must not be