From 8939e209624e81523702c59af1842efa42d1f9d6 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 13 Nov 2020 12:00:35 +0000 Subject: [PATCH 01/12] Default value coercion rules --- spec/Section 3 -- Type System.md | 15 +++++++++++---- spec/Section 6 -- Execution.md | 8 ++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index a31a944f3..a6ee1fa41 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -818,6 +818,9 @@ of rules must be adhered to by every Object type in a GraphQL schema. characters {"__"} (two underscores). 2. The argument must accept a type where {IsInputType(argumentType)} returns {true}. + 3. If the argument has a default value, {defaultValue} must be compatible + with {argumentType} as per the coercion rules for that type, and + coercion of {defaultValue} must not cause an infinite loop. 3. An object type may declare that it implements one or more unique interfaces. 4. An object type must be a super-set of all interfaces it implements: 1. Let this object type be {objectType}. @@ -1520,10 +1523,11 @@ defined by the input object type and for which a value exists. The resulting map is constructed with the following rules: * If no value is provided for a defined input object field and that field - definition provides a default value, the default value should be used. If no - default value is provided and the input object field's type is non-null, an - error should be thrown. Otherwise, if the field is not required, then no entry - is added to the coerced unordered map. + definition provides a default value, the result of coercing the default value + according to the coercion rules of the input field type should be used. + If no default value is provided and the input object field's type is + non-null, an error should be thrown. Otherwise, if the field is not required, + then no entry is added to the coerced unordered map. * If the value {null} was provided for an input object field, and the field's type is not a non-null type, an entry in the coerced unordered map is given @@ -1580,6 +1584,9 @@ Literal Value | Variables | Coerced Value characters {"__"} (two underscores). 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. + 4. If the input field has a default value, {defaultValue} must be compatible + with {inputFieldType} as per the coercion rules for that type, and + coercion of {defaultValue} must not cause an infinite loop. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f6e735bb4..d89d30ed5 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -89,8 +89,10 @@ CoerceVariableValues(schema, operation, variableValues): * Let {value} be the value provided in {variableValues} for the name {variableName}. * If {hasValue} is not {true} and {defaultValue} exists (including {null}): + * Let {coercedDefaultValue} be the result of coercing {defaultValue} according to the + input coercion rules of {variableType}. * Add an entry to {coercedValues} named {variableName} with the - value {defaultValue}. + value {coercedDefaultValue}. * Otherwise if {variableType} is a Non-Nullable type, and either {hasValue} is not {true} or {value} is {null}, throw a query error. * Otherwise if {hasValue} is true: @@ -586,8 +588,10 @@ CoerceArgumentValues(objectType, field, variableValues): name {variableName}. * Otherwise, let {value} be {argumentValue}. * If {hasValue} is not {true} and {defaultValue} exists (including {null}): + * Let {coercedDefaultValue} be the result of coercing {defaultValue} according to the + input coercion rules of {argumentType}. * Add an entry to {coercedValues} named {argumentName} with the - value {defaultValue}. + value {coercedDefaultValue}. * Otherwise if {argumentType} is a Non-Nullable type, and either {hasValue} is not {true} or {value} is {null}, throw a field error. * Otherwise if {hasValue} is true: From 25da05630af7dbd66a525448ecf8f6d921512851 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 19 Feb 2021 17:43:50 +0000 Subject: [PATCH 02/12] Fix 'must not cause an infinite loop' wording. --- spec/Section 3 -- Type System.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index a6ee1fa41..3a66b90e7 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -818,9 +818,8 @@ of rules must be adhered to by every Object type in a GraphQL schema. characters {"__"} (two underscores). 2. The argument must accept a type where {IsInputType(argumentType)} returns {true}. - 3. If the argument has a default value, {defaultValue} must be compatible - with {argumentType} as per the coercion rules for that type, and - coercion of {defaultValue} must not cause an infinite loop. + 3. If the argument has a default value it must be compatible with + {argumentType} as per the coercion rules for that type. 3. An object type may declare that it implements one or more unique interfaces. 4. An object type must be a super-set of all interfaces it implements: 1. Let this object type be {objectType}. @@ -1584,9 +1583,17 @@ Literal Value | Variables | Coerced Value characters {"__"} (two underscores). 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. - 4. If the input field has a default value, {defaultValue} must be compatible - with {inputFieldType} as per the coercion rules for that type, and - coercion of {defaultValue} must not cause an infinite loop. + 4. If the input field has a non-null default value: + 1. {defaultValue} must be compatible with {inputFieldType} as per the + coercion rules for that type. + 2. If the input field references this Input Object either directly or + through referenced Input Objects, all input fields in the chain of + references which reference this Input Object must either: + 1. have no default value; or + 2. have a {null} default value; or + 3. have a default value, {nestedDefaultValue}, such that the value for + this field within {nestedDefaultValue} is either {null} or an empty + list. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. From f73af73cfd778d43e338a3e11cd93b9641f4a266 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 26 Feb 2021 10:23:10 +0000 Subject: [PATCH 03/12] Reorder assertions to prevent infinite loop during coercion --- spec/Section 3 -- Type System.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 3a66b90e7..50b8eab38 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1584,9 +1584,7 @@ Literal Value | Variables | Coerced Value 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. 4. If the input field has a non-null default value: - 1. {defaultValue} must be compatible with {inputFieldType} as per the - coercion rules for that type. - 2. If the input field references this Input Object either directly or + 1. If the input field references this Input Object either directly or through referenced Input Objects, all input fields in the chain of references which reference this Input Object must either: 1. have no default value; or @@ -1594,6 +1592,8 @@ Literal Value | Variables | Coerced Value 3. have a default value, {nestedDefaultValue}, such that the value for this field within {nestedDefaultValue} is either {null} or an empty list. + 2. {defaultValue} must be compatible with {inputFieldType} as per the + coercion rules for that type. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. From 5e793fb5df4e52d096dc6590129604f31fe8d05e Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 26 Feb 2021 10:35:15 +0000 Subject: [PATCH 04/12] Reduce diff --- spec/Section 3 -- Type System.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 50b8eab38..bbc02560a 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1523,10 +1523,10 @@ is constructed with the following rules: * If no value is provided for a defined input object field and that field definition provides a default value, the result of coercing the default value - according to the coercion rules of the input field type should be used. - If no default value is provided and the input object field's type is - non-null, an error should be thrown. Otherwise, if the field is not required, - then no entry is added to the coerced unordered map. + according to the coercion rules of the input field type should be used. If no + default value is provided and the input object field's type is non-null, an + error should be thrown. Otherwise, if the field is not required, then no entry + is added to the coerced unordered map. * If the value {null} was provided for an input object field, and the field's type is not a non-null type, an entry in the coerced unordered map is given From 084b2d55e961d3259cbd191bfb7a8e9ad1c22b05 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 3 May 2021 18:19:34 +0100 Subject: [PATCH 05/12] Clarify the default value cycle detection logic --- spec/Section 3 -- Type System.md | 54 +++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index bbc02560a..aa1b5667c 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1583,21 +1583,53 @@ Literal Value | Variables | Coerced Value characters {"__"} (two underscores). 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. - 4. If the input field has a non-null default value: - 1. If the input field references this Input Object either directly or - through referenced Input Objects, all input fields in the chain of - references which reference this Input Object must either: - 1. have no default value; or - 2. have a {null} default value; or - 3. have a default value, {nestedDefaultValue}, such that the value for - this field within {nestedDefaultValue} is either {null} or an empty - list. - 2. {defaultValue} must be compatible with {inputFieldType} as per the - coercion rules for that type. + 4. Let {fieldSet} be a set containing {inputField}; + {DefaultValueContainsCycle(inputFieldType, defaultValue, fieldSet)} must + return {false}. + 5. {defaultValue} must be compatible with {inputFieldType} as per the + coercion rules for that type. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. +DefaultValueContainsCycle(type, defaultValue, visitedDefaultValueFields): + +- If {defaultValue} does not exist or is null, return {false}. +- If {type} is a non-null type: + - Let {innerType} be the inner type of {type}. + - Return {DefaultValueContainsCycle(innerType, defaultValue, visitedDefaultValueFields)}. +- If {type} is a list type: + - {defaultValue} must be a list. (TODO: should we coerce this to a list?) + - Let {innerType} be the inner type of {type}. + - For each {value} in {defaultValue}: + - If {DefaultValueContainsCycle(innerType, value, visitedDefaultValueFields)}: + - Return {true}. + - Return {false}. +- If {type} is a scalar or enum type: + - Return {false}. +- Assert: {type} is an input object type. +- {defaultValue} must be an object. +- For each field {field} in {type}: + - Let {fieldName} be the name of {field}. + - Let {fieldDefaultValue} be the value for attribute {fieldName} in {defaultValue}. + - If {fieldDefaultValue} does not exist: + - If {field} is within {visitedDefaultValueFields}: + - Return {true}. + - Add {field} to {visitedDefaultValueFields}. + - Let {fieldDefaultValue} be the default value for {field}. + - Let {fieldType} be the expected return type of {field}. + - If {DefaultValueContainsCycle(fieldType, fieldDefaultValue, visitedDefaultValueFields)}: + - Return {true}. +- Return {false}. + +Note: in the above algorithm it's important that {visitedDefaultValueFields} is +passed by value, not by reference, since each path needs its own independent +stack. + +Note: the above algorithm works by determining if the default value on a +particular input object field is referenced more than once in a particular +chain. If it returns {true} (indicating a cycle was found) the object at fault +might not be this object specifically, but one of the objects it references. ### Input Object Extensions From 475d697737ebe967976d561f9092a802b8431a58 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Jan 2022 11:52:29 +0000 Subject: [PATCH 06/12] Rewrite default value cycle algorithm in the style of DetectFragmentCycles --- spec/Section 3 -- Type System.md | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index aa1b5667c..fe02141bb 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1583,44 +1583,44 @@ Literal Value | Variables | Coerced Value characters {"__"} (two underscores). 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. - 4. Let {fieldSet} be a set containing {inputField}; - {DefaultValueContainsCycle(inputFieldType, defaultValue, fieldSet)} must - return {false}. - 5. {defaultValue} must be compatible with {inputFieldType} as per the + 4. Let {fieldSet} be a set containing {inputField}. + 5. {DetectDefaultValueCycle(inputFieldType, defaultValue, fieldSet)}. + 6. {defaultValue} must be compatible with {inputFieldType} as per the coercion rules for that type. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. -DefaultValueContainsCycle(type, defaultValue, visitedDefaultValueFields): +DetectDefaultValueCycle(type, defaultValue, visitedFields): -- If {defaultValue} does not exist or is null, return {false}. +- If {defaultValue} does not exist or is null, return. - If {type} is a non-null type: - Let {innerType} be the inner type of {type}. - - Return {DefaultValueContainsCycle(innerType, defaultValue, visitedDefaultValueFields)}. + - {DetectDefaultValueCycle(innerType, defaultValue, visitedFields)}. + - Return. - If {type} is a list type: - - {defaultValue} must be a list. (TODO: should we coerce this to a list?) + - {defaultValue} must be a list. + - If {defaultValue} is not a list: + - Let {list} be a list containing {defaultValue}. + - Otherwise: + - Let {list} be {defaultValue}. - Let {innerType} be the inner type of {type}. - - For each {value} in {defaultValue}: - - If {DefaultValueContainsCycle(innerType, value, visitedDefaultValueFields)}: - - Return {true}. - - Return {false}. + - For each {value} in {list}: + - {DetectDefaultValueCycle(innerType, value, visitedFields)}. + - Return. - If {type} is a scalar or enum type: - - Return {false}. + - Return. - Assert: {type} is an input object type. - {defaultValue} must be an object. - For each field {field} in {type}: - Let {fieldName} be the name of {field}. - Let {fieldDefaultValue} be the value for attribute {fieldName} in {defaultValue}. - If {fieldDefaultValue} does not exist: - - If {field} is within {visitedDefaultValueFields}: - - Return {true}. - - Add {field} to {visitedDefaultValueFields}. + - {field} must not be withing {visitedFields}. + - Add {field} to {visitedFields}. - Let {fieldDefaultValue} be the default value for {field}. - Let {fieldType} be the expected return type of {field}. - - If {DefaultValueContainsCycle(fieldType, fieldDefaultValue, visitedDefaultValueFields)}: - - Return {true}. -- Return {false}. + - {DetectDefaultValueCycle(fieldType, fieldDefaultValue, visitedFields)}. Note: in the above algorithm it's important that {visitedDefaultValueFields} is passed by value, not by reference, since each path needs its own independent From dde1f7bdf8efc28e7ebde06574fe9c57430ad910 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Jan 2022 12:28:42 +0000 Subject: [PATCH 07/12] Rewrite input object cycle algorithm to match GraphQL.js implementation --- spec/Section 3 -- Type System.md | 72 ++++++++++++++------------------ 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index fe02141bb..346607010 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1584,52 +1584,42 @@ Literal Value | Variables | Coerced Value 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. 4. Let {fieldSet} be a set containing {inputField}. - 5. {DetectDefaultValueCycle(inputFieldType, defaultValue, fieldSet)}. - 6. {defaultValue} must be compatible with {inputFieldType} as per the - coercion rules for that type. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. - -DetectDefaultValueCycle(type, defaultValue, visitedFields): - -- If {defaultValue} does not exist or is null, return. -- If {type} is a non-null type: - - Let {innerType} be the inner type of {type}. - - {DetectDefaultValueCycle(innerType, defaultValue, visitedFields)}. - - Return. -- If {type} is a list type: - - {defaultValue} must be a list. - - If {defaultValue} is not a list: - - Let {list} be a list containing {defaultValue}. - - Otherwise: - - Let {list} be {defaultValue}. - - Let {innerType} be the inner type of {type}. - - For each {value} in {list}: - - {DetectDefaultValueCycle(innerType, value, visitedFields)}. - - Return. -- If {type} is a scalar or enum type: +4. {DetectInputObjectDefaultValueCycle(inputObject)}. + +DetectInputObjectDefaultValueCycle(inputObject, defaultValue, visitedFields): + +- If {defaultValue} is not provided, initialize it to an empty unordered map. +- If {visitedFields} is not provided, initialize it to the empty set. +- If {defaultValue} is a list: + - For each {itemValue} in {defaultValue}: + - {DetectInputObjectDefaultValueCycle(inputObject, itemValue, visitedFields)}. +- Otherwise: + - If {defaultValue} is not an unordered map: + - Return. + - For each field {field} in {inputObject}: + - {DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields)}. + +DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields): + +- Assert: {defaultValue} is an unordered map. +- Let {fieldType} be the type of {field}. +- Let {namedFieldType} be the underlying named type of {fieldType}. +- If {namedFieldType} is not an input object type: - Return. -- Assert: {type} is an input object type. -- {defaultValue} must be an object. -- For each field {field} in {type}: - - Let {fieldName} be the name of {field}. - - Let {fieldDefaultValue} be the value for attribute {fieldName} in {defaultValue}. +- Let {fieldName} be the name of {field}. +- Let {fieldDefaultValue} be the value for {fieldName} in {defaultValue}. +- If {fieldDefaultValue} exists: + - {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, visitedFields)}. +- Otherwise: + - Let {fieldDefaultValue} be the default value of {field}. - If {fieldDefaultValue} does not exist: - - {field} must not be withing {visitedFields}. - - Add {field} to {visitedFields}. - - Let {fieldDefaultValue} be the default value for {field}. - - Let {fieldType} be the expected return type of {field}. - - {DetectDefaultValueCycle(fieldType, fieldDefaultValue, visitedFields)}. - -Note: in the above algorithm it's important that {visitedDefaultValueFields} is -passed by value, not by reference, since each path needs its own independent -stack. - -Note: the above algorithm works by determining if the default value on a -particular input object field is referenced more than once in a particular -chain. If it returns {true} (indicating a cycle was found) the object at fault -might not be this object specifically, but one of the objects it references. + - Return. + - {field} must not be within {visitedFields}. + - Add {field} to {visitedFields}. + - {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, visitedFields)}. ### Input Object Extensions From 12e26d894bfb6f97ac79a6b4ec10564fc6b7fe8a Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Jan 2022 12:30:23 +0000 Subject: [PATCH 08/12] Asterisks --- spec/Section 3 -- Type System.md | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 346607010..168d0e694 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1591,35 +1591,35 @@ Literal Value | Variables | Coerced Value DetectInputObjectDefaultValueCycle(inputObject, defaultValue, visitedFields): -- If {defaultValue} is not provided, initialize it to an empty unordered map. -- If {visitedFields} is not provided, initialize it to the empty set. -- If {defaultValue} is a list: - - For each {itemValue} in {defaultValue}: - - {DetectInputObjectDefaultValueCycle(inputObject, itemValue, visitedFields)}. -- Otherwise: - - If {defaultValue} is not an unordered map: - - Return. - - For each field {field} in {inputObject}: - - {DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields)}. +* If {defaultValue} is not provided, initialize it to an empty unordered map. +* If {visitedFields} is not provided, initialize it to the empty set. +* If {defaultValue} is a list: + * For each {itemValue} in {defaultValue}: + * {DetectInputObjectDefaultValueCycle(inputObject, itemValue, visitedFields)}. +* Otherwise: + * If {defaultValue} is not an unordered map: + * Return. + * For each field {field} in {inputObject}: + * {DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields)}. DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields): -- Assert: {defaultValue} is an unordered map. -- Let {fieldType} be the type of {field}. -- Let {namedFieldType} be the underlying named type of {fieldType}. -- If {namedFieldType} is not an input object type: - - Return. -- Let {fieldName} be the name of {field}. -- Let {fieldDefaultValue} be the value for {fieldName} in {defaultValue}. -- If {fieldDefaultValue} exists: - - {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, visitedFields)}. -- Otherwise: - - Let {fieldDefaultValue} be the default value of {field}. - - If {fieldDefaultValue} does not exist: - - Return. - - {field} must not be within {visitedFields}. - - Add {field} to {visitedFields}. - - {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, visitedFields)}. +* Assert: {defaultValue} is an unordered map. +* Let {fieldType} be the type of {field}. +* Let {namedFieldType} be the underlying named type of {fieldType}. +* If {namedFieldType} is not an input object type: + * Return. +* Let {fieldName} be the name of {field}. +* Let {fieldDefaultValue} be the value for {fieldName} in {defaultValue}. +* If {fieldDefaultValue} exists: + * {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, visitedFields)}. +* Otherwise: + * Let {fieldDefaultValue} be the default value of {field}. + * If {fieldDefaultValue} does not exist: + * Return. + * {field} must not be within {visitedFields}. + * Add {field} to {visitedFields}. + * {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, visitedFields)}. ### Input Object Extensions From 2838c4f739e93bab997ca021364a50cdb76f967b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Jan 2022 12:31:20 +0000 Subject: [PATCH 09/12] Remove unnecessary step --- spec/Section 3 -- Type System.md | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 168d0e694..d2db7629e 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1583,7 +1583,6 @@ Literal Value | Variables | Coerced Value characters {"__"} (two underscores). 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. - 4. Let {fieldSet} be a set containing {inputField}. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. From 9b7bfd525e9718587f58fdfde39e2fcb73386d2b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Jan 2022 12:36:56 +0000 Subject: [PATCH 10/12] Add non-normative note about memoizing default value coercion --- spec/Section 6 -- Execution.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index d89d30ed5..299c6a04c 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -614,6 +614,10 @@ Note: Variable values are not coerced because they are expected to be coerced before executing the operation in {CoerceVariableValues()}, and valid queries must only allow usage of variables of appropriate types. +Note: As an optimization you might choose to coerce each {defaultValue} at +schema build time and cache the results, then refer to this cache within +{CoerceArgumentValues()} calls. + ### Value Resolution From 8beb848ba82fdde4599eccf5d3906ad3dada5365 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 7 Mar 2025 17:12:53 +0000 Subject: [PATCH 11/12] Rather than assertions, use return values. --- spec/Section 3 -- Type System.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 3d4bee962..89575b939 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1706,41 +1706,42 @@ input ExampleInputObject { 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. -4. {DetectInputObjectDefaultValueCycle(inputObject)}. +4. {InputObjectDefaultValueHasCycle(inputObject)} must be {false}. -DetectInputObjectDefaultValueCycle(inputObject, defaultValue, visitedFields): +InputObjectDefaultValueHasCycle(inputObject, defaultValue, visitedFields): - If {defaultValue} is not provided, initialize it to an empty unordered map. - If {visitedFields} is not provided, initialize it to the empty set. - If {defaultValue} is a list: - For each {itemValue} in {defaultValue}: - - {DetectInputObjectDefaultValueCycle(inputObject, itemValue, - visitedFields)}. -- Otherwise: - - If {defaultValue} is not an unordered map: - - Return. + - If {InputObjectDefaultValueHasCycle(inputObject, itemValue, + visitedFields)}, return {true}. +- Otherwise, if {defaultValue} is an unordered map: - For each field {field} in {inputObject}: - - {DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields)}. + - If {InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields)}, + return {true}. +- Return {false}. -DetectInputFieldDefaultValueCycle(field, defaultValue, visitedFields): +InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields): - Assert: {defaultValue} is an unordered map. - Let {fieldType} be the type of {field}. - Let {namedFieldType} be the underlying named type of {fieldType}. - If {namedFieldType} is not an input object type: - - Return. + - Return {false}. - Let {fieldName} be the name of {field}. - Let {fieldDefaultValue} be the value for {fieldName} in {defaultValue}. - If {fieldDefaultValue} exists: - - {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, + - Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue, visitedFields)}. - Otherwise: - Let {fieldDefaultValue} be the default value of {field}. - If {fieldDefaultValue} does not exist: - - Return. - - {field} must not be within {visitedFields}. + - Return {false}. + - If {field} is within {visitedFields}: + - Return {true}. - Add {field} to {visitedFields}. - - {DetectInputObjectDefaultValueCycle(namedFieldType, fieldDefaultValue, + - Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue, visitedFields)}. ### Input Object Extensions From 4f76d6f7a39c55119259bb36a990d6433453cf93 Mon Sep 17 00:00:00 2001 From: Benjie Date: Thu, 22 May 2025 14:11:01 +0100 Subject: [PATCH 12/12] Fix accidental mutation --- spec/Section 3 -- Type System.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 89575b939..47c142998 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1740,9 +1740,10 @@ InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields): - Return {false}. - If {field} is within {visitedFields}: - Return {true}. - - Add {field} to {visitedFields}. + - Let {nextVisitedFields} be a new set containing {field} and everything from + {visitedFields}. - Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue, - visitedFields)}. + nextVisitedFields)}. ### Input Object Extensions