Skip to content

Commit

Permalink
fix(typeEvaluator): fix bug when splatting over optional attributes
Browse files Browse the repository at this point in the history
an optional attribute gets expanded into an union [attributeValue,
null], since we were returning unknown for any nodes that werent an
object the null value made the entire object unknown.
  • Loading branch information
sgulseth committed Jul 25, 2024
1 parent 3ffdd59 commit 92d2449
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 19 deletions.
10 changes: 5 additions & 5 deletions src/typeEvaluator/typeEvaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ function handleObjectSplatNode(
const value = walk({node: attr.value, scope})
$trace('object.splat.value %O', value)
return mapConcrete(value, scope, (node) => {
// if the node is not an object it means we are splating over a non-object, so we return unknown
// splatting over a non-object is a no-op
if (node.type !== 'object') {
return {type: 'unknown'}
return {type: 'object', attributes: {}}
}

const attributes: Record<string, ObjectAttribute> = {}
Expand Down Expand Up @@ -200,17 +200,17 @@ function handleObjectNode(node: ObjectNode, scope: Scope): TypeNode {
if (attr.type === 'ObjectSplat') {
const attributeNode = handleObjectSplatNode(attr, scope)
$trace('object.splat.result %O', attributeNode)

switch (attributeNode.type) {
case 'object': {
splatVariants.push([idx, attributeNode])
continue
}
case 'union': {
for (const node of attributeNode.of) {
// if one of the nodes is unknown we mark the entire object as unknown as we can't infer the type of the object
// eslint-disable-next-line max-depth
if (node.type !== 'object') {
return {type: 'unknown'}
if (node.type === 'unknown') {
return node
}
}
splatVariants.push([idx, attributeNode as UnionTypeNode<ObjectTypeNode>])
Expand Down
47 changes: 47 additions & 0 deletions test/typeEvaluate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2974,6 +2974,53 @@ t.test('splat object with union object', (t) => {
t.matchSnapshot(res)
t.end()
})
t.test('splat on optional object', (t) => {
const query = `*[_type == "author"][0] {
_type,
...optionalObject
}`

const ast = parse(query)
const res = typeEvaluate(ast, schemas)
t.strictSame(
res,
unionOf(
{
type: 'object',
attributes: {
_type: {
type: 'objectAttribute',
value: {
type: 'string',
value: 'author',
},
},
subfield: {
type: 'objectAttribute',
value: {
type: 'string',
},
},
},
},
{
type: 'object',
attributes: {
_type: {
type: 'objectAttribute',
value: {
type: 'string',
value: 'author',
},
},
},
},
{type: 'null'},
),
)

t.end()
})

t.test('function: sanity::versionOf', (t) => {
const query = `*[_type == "author"] {
Expand Down
82 changes: 68 additions & 14 deletions test/typeEvaluateObjects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ const objectVariants: {
},
},
// normal projection attributes end

// MARK: START: Unknown type splatting
{
name: "Test with a type we can't splat over(array)",
attributes: [
Expand Down Expand Up @@ -95,7 +93,15 @@ const objectVariants: {
},
],
expects: {
type: 'unknown',
type: 'object',
attributes: {
A: {
type: 'objectAttribute',
value: {
type: 'boolean',
},
},
},
},
},

Expand Down Expand Up @@ -131,7 +137,15 @@ const objectVariants: {
},
],
expects: {
type: 'unknown',
type: 'object',
attributes: {
A: {
type: 'objectAttribute',
value: {
type: 'boolean',
},
},
},
},
},

Expand All @@ -146,10 +160,59 @@ const objectVariants: {
},
],
expects: {
type: 'unknown',
type: 'object',
attributes: {
A: {
type: 'objectAttribute',
value: {
type: 'boolean',
},
},
},
},
},

{
name: 'Splatting over a non-object only type should result in an empty object',
attributes: [{type: 'ObjectSplat', value: nodeWithType({type: 'string'})}],
expects: {
type: 'object',
attributes: {},
},
},

{
name: 'Splatting over a null union with a object only type should result in an union with the object only type',
attributes: [
{
type: 'ObjectSplat',
value: nodeWithType(
unionOf(
{type: 'null'},
{
type: 'object',
attributes: {
A: {type: 'objectAttribute', value: {type: 'string'}},
},
},
),
),
},
],
expects: unionOf(
{
type: 'object',
attributes: {},
},
{
type: 'object',
attributes: {
A: {type: 'objectAttribute', value: {type: 'string'}},
},
},
),
},

{
name: 'Splatting over unknown type should result in unknown type',
attributes: [
Expand All @@ -160,15 +223,6 @@ const objectVariants: {
type: 'unknown',
},
},
{
name: 'Splatting over a non-object type should result in unknown type',
attributes: [{type: 'ObjectSplat', value: nodeWithType({type: 'string'})}],
expects: {
type: 'unknown',
},
},

// MARK: END: Unknown type splatting

{
name: 'Conditional splat over object with splat',
Expand Down

0 comments on commit 92d2449

Please sign in to comment.