Skip to content
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

[RFC] List coercion algorithm #1058

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
84 changes: 74 additions & 10 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -1771,18 +1771,82 @@ This allows inputs which accept one or many arguments (sometimes referred to as
single value, a client can just pass that value directly rather than
constructing the list.

The result of coercion of a value {value} to a list type {listType} is
{CoerceListValue(value, listType)}.

CoerceListValue(value, listType):

- If {value} is {null}, return {null}.
- Let {itemType} be the inner type of {listType}.
- Let {coercedList} be an empty list.
- If {value} is a list:
- For each {itemValue} in {value}:
- Let {coercedItemValue} be {CoerceListItemValue(itemValue, itemType)}.
- Append {coercedItemValue} to {coercedList}.
- Otherwise:
- Let {coercedItemValue} be {CoerceListItemValue(value, itemType)}.
- Append {coercedItemValue} to {coercedList}.
- Return {coercedList}.

CoerceListItemValue(itemValue, itemType):

- If {itemValue} is {null}, return {null}.
- Otherwise, if {itemValue} is a Variable:
- If the variable provides a runtime value:
- Let {coercedItemValue} be the runtime value of the variable.
- Otherwise, if the variable definition provides a default value:
- Let {coercedItemValue} be this default value.
Comment on lines +1794 to +1798
Copy link
Contributor

@martinbonnin martinbonnin Dec 22, 2023

Choose a reason for hiding this comment

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

Is there a specific reason this doesn't use the "pre-coerced" variable values? (from CoerceVariableValues)

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason was that this is essentially copied from the input coercion for input objects (but via an algorithm to make it clearer): https://spec.graphql.org/draft/#sec-Input-Objects.Input-Coercion

But it's a good question. I guess the reason is that coercedVariableValues is not explicitly made available in section 3 of the spec.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see... It may be an issue:

Ultra synthetic example:

type Query {
  a(b: [[Int]]): Int
}

Operation:

query Foo($c: [Int]) {
  a(b: [$c])
}

Runtime Variables:

{
  "c": 42
}

If we're saying the runtime value is the "value that is sent over the wire", we end up with b = [42] (incompatible) instead of b = [[42]] if we coerce the variable to a list first (I think?)
If we're saying the runtime value is the "pre-coerced" value, then there's no need to mention defaultValue

- Otherwise:
- Let {coercedItemValue} be {null}.
- If {coercedItemValue} is {null} and {itemType} is a non-null type, a _field
error_ must be raised.
- Return {coercedItemValue}.
- Otherwise, return the result of coercing {itemValue} according to the input
coercion rules for {itemType}.

Note: When a default value exists for a variable definition, the type of the
variable is allowed to be nullable even if it is used in a non-nullable
position, see
[Allowing Optional Variables When Default Values Exist](#sec-All-Variable-Usages-Are-Allowed.Allowing-Optional-Variables-When-Default-Values-Exist)
in Validation. If the value for such a variable is explicitly {null} and is used
as the value for a list item of non-nullable type then a _field error_ will be
raised.

Following are examples of input coercion with various list types and values:

| Expected Type | Provided Value | Coerced Value |
| ------------- | ---------------- | --------------------------- |
| `[Int]` | `[1, 2, 3]` | `[1, 2, 3]` |
| `[Int]` | `[1, "b", true]` | Error: Incorrect item value |
| `[Int]` | `1` | `[1]` |
| `[Int]` | `null` | `null` |
| `[[Int]]` | `[[1], [2, 3]]` | `[[1], [2, 3]]` |
| `[[Int]]` | `[1, 2, 3]` | Error: Incorrect item value |
| `[[Int]]` | `1` | `[[1]]` |
| `[[Int]]` | `null` | `null` |
| Expected Type | Literal Value | Variable Values | Coerced Value |
| ------------- | ---------------- | --------------- | ---------------------------- |
| `[Int]` | `[1, 2, 3]` | `{}` | `[1, 2, 3]` |
| `[Int]` | `[1, null]` | `{}` | `[1, null]` |
| `[Int]` | `[1, "b", true]` | `{}` | Error: Incorrect item value |
| `[Int]` | `1` | `{}` | `[1]` |
| `[Int]` | `null` | `{}` | `null` |
| `[Int]` | `[1, $b]` | `{}` | `[1, null]` |
| `[Int]` | `[1, $b]` | `{"b": 2}` | `[1, 2]` |
| `[Int]` | `[1, $b]` | `{"b": null}` | `[1, null]` |
| `[Int]!` | `[null]` | `{}` | `[null]` |
| `[Int]!` | `null` | `{}` | Error: Must be non-null |
| `[Int!]` | `[1, 2, 3]` | `{}` | `[1, 2, 3]` |
| `[Int!]` | `[1, null]` | `{}` | Error: Item must be non-null |
| `[Int!]` | `[1, "b", true]` | `{}` | Error: Incorrect item value |
| `[Int!]` | `1` | `{}` | `[1]` |
| `[Int!]` | `null` | `{}` | `null` |
| `[Int!]` | `[1, $b]` | `{}` | Error: Item must be non-null |
| `[Int!]` | `[1, $b]` | `{"b": 2}` | `[1, 2]` |
| `[Int!]` | `[1, $b]` | `{"b": null}` | Error: Item must be non-null |
| `[[Int]]` | `[[1], [2, 3]]` | `{}` | `[[1], [2, 3]]` |
| `[[Int]]` | `[1, 2, 3]` | `{}` | `[[1], [2], [3]]` |
| `[[Int]]` | `[1, [2], 3]` | `{}` | `[[1], [2], [3]]` |
| `[[Int]]` | `[1, null, 3]` | `{}` | `[[1], null, [3]]` |
| `[[Int]]` | `[[1], ["b"]]` | `{}` | Error: Incorrect item value |
| `[[Int]]` | `1` | `{}` | `[[1]]` |
| `[[Int]]` | `null` | `{}` | `null` |
| `[[Int]]` | `[1, [$b]]` | `{}` | `[[1],[null]]` |
| `[[Int]]` | `[1, [$b]]` | `{"b": null}` | `[[1],[null]]` |
| `[[Int]]` | `[1, [$b]]` | `{"b": 2}` | `[[1],[2]]` |
| `[[Int]]` | `[1, $b]` | `{"b": [2]}` | `[[1],[2]]` |
| `[[Int]]` | `[1, $b]` | `{"b": 2}` | `[[1],[2]]` |
| `[[Int]]` | `[1, $b]` | `{"b": null}` | `[[1],null]` |

## Non-Null

Expand Down