diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 446e9c04d..23ddf92d1 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -172,16 +172,11 @@ FragmentSpread : ... FragmentName Arguments? Directives? InlineFragment : ... TypeCondition? Directives? SelectionSet -FragmentDefinition : fragment FragmentName FragmentArgumentsDefinition? -TypeCondition Directives? SelectionSet +FragmentDefinition : fragment FragmentName VariablesDefinition? TypeCondition +Directives? SelectionSet FragmentName : Name but not `on` -FragmentArgumentsDefinition : ( FragmentArgumentDefinition+ ) - -FragmentArgumentDefinition : Description? Variable : Type DefaultValue? -Directives[Const]? - TypeCondition : on NamedType Value[Const] : @@ -401,7 +396,6 @@ ExecutableDirectiveLocation : one of - `FRAGMENT_SPREAD` - `INLINE_FRAGMENT` - `VARIABLE_DEFINITION` -- `FRAGMENT_ARGUMENT_DEFINITION` TypeSystemDirectiveLocation : one of diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 270510314..599987396 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -518,8 +518,8 @@ which returns the result: FragmentSpread : ... FragmentName Arguments? Directives? -FragmentDefinition : fragment FragmentName FragmentArgumentsDefinition? -TypeCondition Directives? SelectionSet +FragmentDefinition : fragment FragmentName VariablesDefinition? TypeCondition +Directives? SelectionSet FragmentName : Name but not `on` @@ -1212,21 +1212,15 @@ size `60`: Variables can be used within fragments. Operation-defined variables have global scope with a given operation, so a variable used within a fragment must either be declared in any top-level operation that transitively consumes that fragment, -or by that same fragment as a fragment argument. If a variable is referenced in -a fragment that does not define it as an argument and is included by an -operation that does not define that variable, that operation is invalid (see +or by that same fragment as a fragment variable definition. If a variable is +referenced in a fragment is included by an operation where neither the fragment +nor the operaiton defines that variable, that operation is invalid (see [All Variable Uses Defined](#sec-All-Variable-Uses-Defined)). -## Fragment Arguments +## Fragment Variable Definitions -FragmentArgumentsDefinition : ( FragmentArgumentDefinition+ ) - -FragmentArgumentDefinition : Description? Variable : Type DefaultValue? -Directives[Const]? - -Fragments may define locally scoped arguments, which can be used in locations -that accept variables. This allows fragments to be reused while enabling the -caller to specify the fragment's behavior. +Fragments may define locally scoped variables. This allows fragments to be +reused while enabling the caller to specify the fragment's behavior. For example, the profile picture may need to be a different size depending on the parent context: @@ -1251,8 +1245,8 @@ fragment dynamicProfilePic($size: Int! = 50) on User { In this case the `user` will have a larger `profilePic` than those found in the list of `friends`. -A fragment argument is scoped to the fragment that defines it. Fragment -arguments are allowed to shadow operation variables. +A fragment-defined variable is scoped to the fragment that defines it. +Fragment-defined variables are allowed to shadow operation-defined variables. ```graphql example query withShadowedVariables($size: Int) { diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index bc47d9f32..cce64b857 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1876,7 +1876,6 @@ ExecutableDirectiveLocation : one of - `FRAGMENT_SPREAD` - `INLINE_FRAGMENT` - `VARIABLE_DEFINITION` -- `FRAGMENT_ARGUMENT_DEFINITION` TypeSystemDirectiveLocation : one of diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 522b5928a..3054a9f6c 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -206,7 +206,6 @@ enum __DirectiveLocation { FRAGMENT_SPREAD INLINE_FRAGMENT VARIABLE_DEFINITION - FRAGMENT_ARGUMENT_DEFINITION SCHEMA SCALAR OBJECT @@ -476,7 +475,6 @@ supported. All possible locations are listed in the `__DirectiveLocation` enum: - {"FRAGMENT_SPREAD"} - {"INLINE_FRAGMENT"} - {"VARIABLE_DEFINITION"} -- {"FRAGMENT_ARGUMENT_DEFINITION"} - {"SCHEMA"} - {"SCALAR"} - {"OBJECT"} diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 24e850875..3f9d7449f 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -419,7 +419,8 @@ fragment directFieldSelectionOnUnion on CatOrDog { FieldsInSetCanMerge(set): - Let {visitedSelections} be the selections in {set} including visiting - fragments and inline fragments an applying any supplied fragment arguments. + fragments and inline fragments and applying any supplied fragment spread + arguments. - Let {spreadsForName} be the set of fragment spreads with a given name in {visitedSelections}. - Given each pair of members {spreadA} and {spreadB} in {spreadsForName}: @@ -576,7 +577,7 @@ fragment conflictingDifferingResponses on Pet { } ``` -Fragment arguments can also cause fields to fail to merge. +Fragment spread arguments can also cause fields to fail to merge. While the following is valid: @@ -599,11 +600,11 @@ fragment safeFragmentArguments on Dog { ``` it is only valid because `safeFragmentArguments` uses -`potentiallyConflictingArguments` with the same value for `commandOne` and -`commandTwo`. Therefore `commandFragment` resolves `doesKnowCommand`'s -`dogCommand:` arg to `SIT` in both cases. +`potentiallyConflictingArguments` with the same value for the fragment-defined +variables `commandOne` and `commandTwo`. Therefore `commandFragment` resolves +`doesKnowCommand`'s `dogCommand` argument value to `SIT` in both cases. -However, by changing the argument values: +However, by changing the fragment spread argument values: ```graphql counter-example fragment conflictingFragmentArguments on Dog { @@ -706,14 +707,16 @@ validation rules apply in each case. - For each {argument} in the document: - Let {argumentName} be the Name of {argument}. - - Let {argumentDefinition} be the argument definition provided by the parent - field, fragment definition or directive definition named {argumentName}. + - If the parent is a field or directive: + - Let {argumentDefinition} be the argument or variable definition named + {argumentName} provided by the parent field definition, directive definition + or fragment definition. - {argumentDefinition} must exist. **Explanatory Text** -Every argument provided to a field or directive must be defined in the set of -possible arguments of that field or directive. +Every argument provided to a field or directive or fragment spread must be +defined in the set of possible arguments of that field, directive or fragment. For example the following are valid: @@ -744,7 +747,7 @@ fragment invalidArgName on Dog { } ``` -and this is also invalid as the argument `dogCommand` is not defined on fragment +and this is also invalid as the variable `dogCommand` is not defined on fragment `withFragmentArg`. ```graphql counter-example @@ -810,10 +813,10 @@ ambiguous and invalid. #### Required Arguments - For each Field, Fragment Spread or Directive in the document: - - Let {arguments} be the arguments provided by the Field, Fragment Spread or - Directive. - - Let {argumentDefinitions} be the set of argument definitions of that Field, - Fragment Spread or Directive. + - Let {arguments} be the arguments provided by the Field, Directive or + Fragment Spread. + - Let {argumentDefinitions} be the set of argument definitions of that Field + or Directive, or the variable definitions of that Fragment. - For each {argumentDefinition} in {argumentDefinitions}: - Let {type} be the expected type of {argumentDefinition}. - Let {defaultValue} be the default value of {argumentDefinition}. @@ -1592,18 +1595,19 @@ query ($foo: Boolean = true, $bar: Boolean = false) { **Formal Specification** -- For every {operation} in the document: - - For every {variable} defined on {operation}: +- For every {operation} and {fragment} in the document: + - Let {operationOrFragment} be that {operation} or {fragment}. + - For every {variable} defined on {operationOrFragment}: - Let {variableName} be the name of {variable}. - Let {variables} be the set of all variables named {variableName} on - {operation}. + {operationOrFragment}. - {variables} must be a set of one. **Explanatory Text** -If any operation defines more than one variable with the same name, it is -ambiguous and invalid. It is invalid even if the type of the duplicate variable -is the same. +If any operation or fragment defines more than one variable with the same name, +it is ambiguous and invalid. It is invalid even if the type of the duplicate +variable is the same. ```graphql counter-example query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) { @@ -1632,12 +1636,36 @@ fragment HouseTrainedFragment on Query { } ``` +Likewise, it is valid for both an operation and a fragment to define a variable +with the same name: + +```graphql example +query C($atOtherHomes: Boolean) { + ...HouseTrainedFragment + aDog: dog { + ...HouseTrainedDog + } +} + +fragment HouseTrainedDog($atOtherHomes: Boolean) on Dog { + isHouseTrained(atOtherHomes: $atOtherHomes) +} +``` + +Fragment-defined variables are scoped locally to the fragment that defines them, +and override any operation-defined variable values, so there is never ambiguity +about which value to use. In this case, the value of the argument `atOtherHomes` +within `HouseTrainedFragment` will be the operation-set value, and within +`HouseTrainedDog` will resolve to `null`, as the argument is not set by the +fragment spread in the query `C`. + ### Variables Are Input Types **Formal Specification** -- For every {operation} in a {document}: - - For every {variable} on each {operation}: +- For every {operation} and {fragment} in a {document}: + - Let {operationOrFragment} be that {operation} or {fragment}. + - For every {variable} defined on {operationOrFragment}: - Let {variableType} be the type of {variable}. - {IsInputType(variableType)} must be {true}. @@ -1705,13 +1733,14 @@ query takesCatOrDog($catOrDog: CatOrDog) { transitively. - For each {fragment} in {fragments}: - For each {variableUsage} in scope of {fragment}, variable must be in - {operation}'s variable list. + {fragment}'s or {operation}'s variable list. **Explanatory Text** -Variables are scoped on a per-operation basis. That means that any variable used -within the context of an operation must be defined at the top level of that -operation +Operation-defined Variables are scoped on a per-operation basis, while +Fragment-defined Variables are scoped locally to the fragment. That means that +any variable used within the context of an operation must either be defined at +the top level of that operation or on the fragment that uses that variable. For example: @@ -1738,9 +1767,10 @@ query variableIsNotDefined { ${atOtherHomes} is not defined by the operation. Fragments complicate this rule. Any fragment transitively included by an -operation has access to the variables defined by that operation. Fragments can -appear within multiple operations and therefore variable usages must correspond -to variable definitions in all of those operations. +operation has access to the variables defined by that operation and defined on +the fragment. Fragments can appear within multiple operations and therefore +variable usages not defined on the fragment must correspond to variable +definitions in all of those operations. For example the following is valid: @@ -1837,7 +1867,7 @@ This is because {houseTrainedQueryTwoNotDefined} does not define a variable ${atOtherHomes} but that variable is used by {isHouseTrainedFragment} which is included in that operation. -### All Variables Used +### All Operation Variables Used **Formal Specification** @@ -1945,20 +1975,21 @@ fragment isHouseTrainedFragment on Dog { This document is not valid because {queryWithExtraVar} defines an extraneous variable. -### All Fragment Arguments Used +### All Fragment Variables Used **Formal Specification** - For every {fragment} in the document: - - Let {arguments} be the arguments defined by that {fragment}. - - Each {argument} in {arguments} must be used at least once in the fragment's + - Let {variables} be the variables defined by that {fragment}. + - Each {variable} in {variables} must be used at least once in the fragment's scope. **Explanatory Text** -All arguments defined by a fragment must be used in that same fragment. Because -fragment arguments are scoped to the fragment they are defined on, if the -fragment does not use the argument, then the argument is superfluous. +All variables defined by a fragment must be used in that same fragment. Because +fragment-defined variables are scoped to the fragment they are defined on, if +the fragment does not use the variable, then the variable definition is +superfluous. For example, the following is invalid: @@ -1974,10 +2005,10 @@ fragment fragmentArgUnused($atOtherHomes: Boolean) on Dog { } ``` -This document is invalid because even though `fragmentArgUnused` is spread with -the argument `atOtherHomes`, and even though `$atOtherHomes` is defined as an -operation variable, there is never a variable `$atOtherHomes` used within the -scope of `fragmentArgUnused`. +This document is invalid: even though `fragmentArgUnused` is spread with the +argument `atOtherHomes` and `$atOtherHomes` is defined as an operation variable, +there is never a variable `$atOtherHomes` used within the scope of +`fragmentArgUnused`. ### All Variable Usages Are Allowed @@ -1987,9 +2018,9 @@ scope of `fragmentArgUnused`. - Let {variableUsages} be all usages transitively included in the {operation}. - For each {variableUsage} in {variableUsages}: - Let {variableName} be the name of {variableUsage}. - - If the usage is within a {fragment} that defines an argument of - {variableName}: - - Let {variableDefinition} be the {ArgumentDefinition} named + - If the usage is within a {fragment} that defines a {variableDefinition} + for {variableName}: + - Let {variableDefinition} be the {VariableDefinition} named {variableName} defined within {fragment}. - Otherwise, let {variableDefinition} be the {VariableDefinition} named {variableName} defined within {operation}. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index ee97c5cb2..6442ebbc8 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -332,11 +332,10 @@ First, the selection set is turned into a grouped field set; then, each represented field in the grouped field set produces an entry into a response map. -ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues, -argumentValues): +ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - Let {groupedFieldSet} be the result of {CollectFields(objectType, - selectionSet, variableValues, argumentValues)}. + selectionSet, variableValues)}. - Initialize {resultMap} to an empty ordered map. - For each {groupedFieldSet} as {responseKey} and {fields}: - Let {fieldName} be the name of the first entry in {fields}. Note: This value @@ -345,7 +344,7 @@ argumentValues): {objectType}. - If {fieldType} is defined: - Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType, - fields, variableValues, argumentValues)}. + fields, variableValues)}. - Set {responseValue} as the value for {responseKey} in {resultMap}. - Return {resultMap}. @@ -491,7 +490,7 @@ The depth-first-search order of the field groups produced by {CollectFields()} is maintained through execution, ensuring that fields appear in the executed response in a stable and predictable order. -CollectFields(objectType, selectionSet, variableValues, argumentValues, +CollectFields(objectType, selectionSet, variableValues, visitedFragments): - If {visitedFragments} is not provided, initialize it to the empty set. @@ -519,20 +518,18 @@ visitedFragments): {fragmentSpreadName}. - If no such {fragment} exists, continue with the next {selection} in {selectionSet}. - - Let {spreadArgumentValues} be the result of calling - {ArgumentsFromSpread(selection, fragment, variableValues, argumentValues)} - - Let {fragmentSpreadKey} be a unique key of {fragmentSpreadName} and - {spreadArgumentValues}. + - Let {arguments} be the set of arguments on {selection}. + - Let {fragmentSpreadKey} be a unique key of {fragmentSpreadName} and {arguments}. - If {fragmentSpreadKey} is in {visitedFragments}, continue with the next {selection} in {selectionSet}. - Add {fragmentSpreadKey} to {visitedFragments}. - Let {fragmentType} be the type condition on {fragment}. - If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue with the next {selection} in {selectionSet}. - - Let {fragmentSelectionSet} be the top-level selection set of {fragment}. + - Let {fragmentWithArgumentSubstitutions} be the result of calling {SubstituteFragmentArguments(fragment, arguments)}. + - Let {fragmentSelectionSet} be the top-level selection set of {fragmentWithArgumentSubstitutions}. - Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, - spreadArgumentValues, visitedFragments)}. + {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. - For each {fragmentGroup} in {fragmentGroupedFieldSet}: - Let {responseKey} be the response key shared by all fields in {fragmentGroup}. @@ -546,8 +543,7 @@ visitedFragments): {selectionSet}. - Let {fragmentSelectionSet} be the top-level selection set of {selection}. - Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, - argumentValues, visitedFragments)}. + {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. - For each {fragmentGroup} in {fragmentGroupedFieldSet}: - Let {responseKey} be the response key shared by all fields in {fragmentGroup}. @@ -556,14 +552,6 @@ visitedFragments): - Append all items in {fragmentGroup} to {groupForResponseKey}. - Return {groupedFields}. -ArgumentsFromSpread(fragmentSpread, fragment, variableValues, -parentArgumentValues): - -- Let {argumentDefinitions} be the arguments defined on {fragment} -- Let {spreadArguments} be the arguments set on {fragmentSpread} -- Return the result of {CoerceArgumentValues(argumentDefinitions, - spreadArguments, variableValues, parentArgumentValues)} - DoesFragmentTypeApply(objectType, fragmentType): - If {fragmentType} is an Object Type: @@ -576,9 +564,30 @@ DoesFragmentTypeApply(objectType, fragmentType): - if {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. +SubstituteFragmentArguments(fragment, arguments): + +- Let {variablesToSubstitute} be initialized to an empty map. +- For each {variableDefinition} in {fragment}: + - Let {variableName} be the name of {variableDefinition}. + - If {variableName} is a key in {arguments}: + - Let {argumentValue} be the value of {variableName} in {arguments}. + - Add {argumentValue} to {variablesToSubstitute} at key {variableName}. + - Otherwise if {variableDefinition} has a default value {defaultValue}: + - Add {defaultValue} to {variablesToSubstitute} at key {variableName}. + - Otherwise: + - Set the key {variableName} in {variableToSubstitute} to a value indicating the variable is unset. +- Let {substitutedFragment} be a copy of {fragment} where: + - For each {variable} in the selection set of {fragment}: + - Let {variableUsageName} be the name of {variable}. + - If {variableUsageName} is in {variablesToSubstitute}: + - Replace {variable} with the value of {variableUsageName} in {variablesToSubstitute}. +- Return {substitutedFragment}. + Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. +Note: The unset value used to replace unset fragment-defined arguments in {SubstituteFragmentArguments()} must not be a variable defined by any operation that includes the fragment. An example would be to use a variable with a reserved prefix, like `$__UNSET`, to replace all unset fragment-defined variables. + ## Executing Fields Each field requested in the grouped field set that is defined on the selected @@ -587,17 +596,16 @@ coerces any provided argument values, then resolves a value for the field, and finally completes that value either by recursively executing another selection set or coercing a scalar value. -ExecuteField(objectType, objectValue, fieldType, fields, variableValues, -fragmentArgumentValues): +ExecuteField(objectType, objectValue, fieldType, fields, variableValues): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. - Let {argumentValues} be the result of {CoerceFieldArgumentValues(objectType, - field, variableValues, fragmentArgumentValues)} + field, variableValues)} - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. - Return the result of {CompleteValue(fieldType, fields, resolvedValue, - variableValues, fragmentArgumentValues)}. + variableValues)}. ### Coercing Field Arguments @@ -608,18 +616,16 @@ the type system to have a specific input type. At each argument position in an operation may be a literal {Value}, or a {Variable} to be provided at runtime. -CoerceFieldArgumentValues(objectType, field, variableValues, -fragmentArgumentValues): +CoerceFieldArgumentValues(objectType, field, variableValues): - Let {argumentValues} be the argument values provided in {field}. - Let {fieldName} be the name of {field}. - Let {argumentDefinitions} be the arguments defined by {objectType} for the field named {fieldName}. - Return {CoerceArgumentValues(argumentDefinitions, argumentValues, - variableValues, fragmentArgumentValues)} + variableValues)} -CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues, -fragmentArgumentValues): +CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues): - For each {argumentDefinition} in {argumentDefinitions}: - Let {argumentName} be the name of {argumentDefinition}. @@ -631,15 +637,10 @@ fragmentArgumentValues): {argumentName}. - If {argumentValue} is a {Variable}: - Let {variableName} be the name of {argumentValue}. - - If {fragmentArgumentValues} provides a value for the name {variableName}: - - Let {hasValue} be {true}. - - Let {value} be the value provided in {fragmentArgumentValues} for the - name {variableName}. - - Otherwise if {variableValues} provides a value for the name - {variableName}: - - Let {hasValue} be {true}. - - Let {value} be the value provided in {variableValues} for the name - {variableName}. + - Let {hasValue} be {true} if {variableValues} provides a value for the name + {variableName}. + - Let {value} be the value provided in {variableValues} for the name + {variableName}. - Otherwise, let {value} be {argumentValue}. - If {hasValue} is not {true} and {defaultValue} exists (including {null}): - Add an entry to {coercedValues} named {argumentName} with the value @@ -695,12 +696,12 @@ After resolving the value for a field, it is completed by ensuring it adheres to the expected return type. If the return type is another Object type, then the field execution process continues recursively. -CompleteValue(fieldType, fields, result, variableValues, argumentValues): +CompleteValue(fieldType, fields, result, variableValues): - If the {fieldType} is a Non-Null type: - Let {innerType} be the inner type of {fieldType}. - Let {completedResult} be the result of calling {CompleteValue(innerType, - fields, result, variableValues, argumentValues)}. + fields, result, variableValues)}. - If {completedResult} is {null}, raise a _field error_. - Return {completedResult}. - If {result} is {null} (or another internal value similar to {null} such as @@ -709,8 +710,7 @@ CompleteValue(fieldType, fields, result, variableValues, argumentValues): - If {result} is not a collection of values, raise a _field error_. - Let {innerType} be the inner type of {fieldType}. - Return a list where each list item is the result of calling - {CompleteValue(innerType, fields, resultItem, variableValues, - argumentValues)}, where {resultItem} is each item in {result}. + {CompleteValue(innerType, fields, resultItem, variableValues)}, where {resultItem} is each item in {result}. - If {fieldType} is a Scalar or Enum type: - Return the result of {CoerceResult(fieldType, result)}. - If {fieldType} is an Object, Interface, or Union type: @@ -720,7 +720,7 @@ CompleteValue(fieldType, fields, result, variableValues, argumentValues): - Let {objectType} be {ResolveAbstractType(fieldType, result)}. - Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}. - Return the result of evaluating {ExecuteSelectionSet(subSelectionSet, - objectType, result, variableValues, argumentValues)} _normally_ (allowing + objectType, result, variableValues)} _normally_ (allowing for parallelization). **Coercing Results**