Skip to content

Commit 160bcf5

Browse files
tyao1meta-codesync[bot]
authored andcommitted
Fix incremental build missing mutation field return type changes
Reviewed By: evanyeung Differential Revision: D97396237 fbshipit-source-id: a810c53cc55e695df0677b3fdb108d9bb328f90e
1 parent 9645a6b commit 160bcf5

File tree

4 files changed

+202
-1
lines changed

4 files changed

+202
-1
lines changed

compiler/crates/dependency-analyzer/src/schema_change_analyzer.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ impl Visitor for SchemaChangeDefinitionFinder<'_, '_> {
257257
const VISIT_ARGUMENTS: bool = true;
258258
const VISIT_DIRECTIVES: bool = true;
259259

260+
fn visit_operation(&mut self, operation: &OperationDefinition) {
261+
self.add_type_changes(operation.type_);
262+
self.default_visit_operation(operation);
263+
}
264+
260265
fn visit_linked_field(&mut self, field: &LinkedField) {
261266
let id = field.definition.item;
262267
let type_ = self.schema.field(id).type_.inner();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
==================================== INPUT ====================================
2+
# When a mutation field's return type changes (e.g. from PayloadA to PayloadB),
3+
# the incremental build must regenerate the mutation artifact so the
4+
# concreteType in the normalization AST reflects the new return type.
5+
# This is a regression test for a bug where changing Mutation's field return
6+
# type was classified as ObjectChanged("Mutation"), but the
7+
# SchemaChangeDefinitionFinder only checked child return types — not the
8+
# operation's root type — so no definitions were marked as affected.
9+
10+
//- foo.js
11+
graphql`
12+
mutation fooMutation($input: CreateInput!) {
13+
createThing(input: $input) {
14+
success
15+
message
16+
}
17+
}
18+
`
19+
20+
//- relay.config.json
21+
{
22+
"language": "typescript",
23+
"schema": "./schema.graphql"
24+
}
25+
26+
//- schema.graphql
27+
type Query { me: User }
28+
type User { name: String }
29+
type Mutation { createThing(input: CreateInput!): PayloadA }
30+
input CreateInput { key: String }
31+
type PayloadA { success: Boolean, message: String }
32+
type PayloadB { success: Boolean, message: String }
33+
34+
//-++ schema.graphql
35+
type Query { me: User }
36+
type User { name: String }
37+
type Mutation { createThing(input: CreateInput!): PayloadB }
38+
input CreateInput { key: String }
39+
type PayloadA { success: Boolean, message: String }
40+
type PayloadB { success: Boolean, message: String }
41+
==================================== OUTPUT ===================================
42+
//-++ __generated__/fooMutation.graphql.ts
43+
/**
44+
* <auto-generated> SignedSource<<09380048fd372e3b07708ff0e06cb1e9>>
45+
* @lightSyntaxTransform
46+
* @nogrep
47+
*/
48+
49+
/* tslint:disable */
50+
/* eslint-disable */
51+
// @ts-nocheck
52+
53+
import { ConcreteRequest } from 'relay-runtime';
54+
export type CreateInput = {
55+
key?: string | null | undefined;
56+
};
57+
export type fooMutation$variables = {
58+
input: CreateInput;
59+
};
60+
export type fooMutation$data = {
61+
readonly createThing: {
62+
readonly message: string | null | undefined;
63+
readonly success: boolean | null | undefined;
64+
} | null | undefined;
65+
};
66+
export type fooMutation = {
67+
response: fooMutation$data;
68+
variables: fooMutation$variables;
69+
};
70+
71+
const node: ConcreteRequest = (function(){
72+
var v0 = [
73+
{
74+
"defaultValue": null,
75+
"kind": "LocalArgument",
76+
"name": "input"
77+
}
78+
],
79+
v1 = [
80+
{
81+
"alias": null,
82+
"args": [
83+
{
84+
"kind": "Variable",
85+
"name": "input",
86+
"variableName": "input"
87+
}
88+
],
89+
"concreteType": "PayloadB",
90+
"kind": "LinkedField",
91+
"name": "createThing",
92+
"plural": false,
93+
"selections": [
94+
{
95+
"alias": null,
96+
"args": null,
97+
"kind": "ScalarField",
98+
"name": "success",
99+
"storageKey": null
100+
},
101+
{
102+
"alias": null,
103+
"args": null,
104+
"kind": "ScalarField",
105+
"name": "message",
106+
"storageKey": null
107+
}
108+
],
109+
"storageKey": null
110+
}
111+
];
112+
return {
113+
"fragment": {
114+
"argumentDefinitions": (v0/*:: as any*/),
115+
"kind": "Fragment",
116+
"metadata": null,
117+
"name": "fooMutation",
118+
"selections": (v1/*:: as any*/),
119+
"type": "Mutation",
120+
"abstractKey": null
121+
},
122+
"kind": "Request",
123+
"operation": {
124+
"argumentDefinitions": (v0/*:: as any*/),
125+
"kind": "Operation",
126+
"name": "fooMutation",
127+
"selections": (v1/*:: as any*/)
128+
},
129+
"params": {
130+
"cacheID": "9ec2b5d3d80ddd1645b646b3d3e38cde",
131+
"id": null,
132+
"metadata": {},
133+
"name": "fooMutation",
134+
"operationKind": "mutation",
135+
"text": "mutation fooMutation(\n $input: CreateInput!\n) {\n createThing(input: $input) {\n success\n message\n }\n}\n"
136+
}
137+
};
138+
})();
139+
140+
(node as any).hash = "ac2677409f018dbc54fdbd5df32c78cc";
141+
142+
export default node;
143+
144+
145+
146+
Artifact Map:
147+
Project: default
148+
Type: Mapping
149+
- Source: ExecutableDefinition: fooMutation
150+
Path: __generated__/fooMutation.graphql.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# When a mutation field's return type changes (e.g. from PayloadA to PayloadB),
2+
# the incremental build must regenerate the mutation artifact so the
3+
# concreteType in the normalization AST reflects the new return type.
4+
# This is a regression test for a bug where changing Mutation's field return
5+
# type was classified as ObjectChanged("Mutation"), but the
6+
# SchemaChangeDefinitionFinder only checked child return types — not the
7+
# operation's root type — so no definitions were marked as affected.
8+
9+
//- foo.js
10+
graphql`
11+
mutation fooMutation($input: CreateInput!) {
12+
createThing(input: $input) {
13+
success
14+
message
15+
}
16+
}
17+
`
18+
19+
//- relay.config.json
20+
{
21+
"language": "typescript",
22+
"schema": "./schema.graphql"
23+
}
24+
25+
//- schema.graphql
26+
type Query { me: User }
27+
type User { name: String }
28+
type Mutation { createThing(input: CreateInput!): PayloadA }
29+
input CreateInput { key: String }
30+
type PayloadA { success: Boolean, message: String }
31+
type PayloadB { success: Boolean, message: String }
32+
33+
//-++ schema.graphql
34+
type Query { me: User }
35+
type User { name: String }
36+
type Mutation { createThing(input: CreateInput!): PayloadB }
37+
input CreateInput { key: String }
38+
type PayloadA { success: Boolean, message: String }
39+
type PayloadB { success: Boolean, message: String }

compiler/crates/relay-compiler/tests/relay_compiler_integration_test.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<ecd8eb6892f4e4f149adb77cb2b3ef0a>>
7+
* @generated SignedSource<<ec420c2ce9c33bcedd2ab80b8b83f4a3>>
88
*/
99

1010
mod relay_compiler_integration;
@@ -250,6 +250,13 @@ async fn incremental_input_object_removed() {
250250
test_fixture(transform_fixture, file!(), "incremental_input_object_removed.input", "relay_compiler_integration/fixtures/incremental_input_object_removed.expected", input, expected).await;
251251
}
252252

253+
#[tokio::test]
254+
async fn incremental_mutation_field_return_type_change() {
255+
let input = include_str!("relay_compiler_integration/fixtures/incremental_mutation_field_return_type_change.input");
256+
let expected = include_str!("relay_compiler_integration/fixtures/incremental_mutation_field_return_type_change.expected");
257+
test_fixture(transform_fixture, file!(), "incremental_mutation_field_return_type_change.input", "relay_compiler_integration/fixtures/incremental_mutation_field_return_type_change.expected", input, expected).await;
258+
}
259+
253260
#[tokio::test]
254261
async fn incremental_object_added_with_interface() {
255262
let input = include_str!("relay_compiler_integration/fixtures/incremental_object_added_with_interface.input");

0 commit comments

Comments
 (0)