From cebc7f0650eec1d9438ce6c2b5f59d0f7b8226fa Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 5 Feb 2024 11:16:06 -0800 Subject: [PATCH] Experimental support for @semanticNonNull (#4601) Summary: This PR adds experimental support for `semanticNonNull` as described in https://github.com/apollographql/specs/pull/42. Which is part of a broader effort to explore semantic nullability in GraphQL as explored in [RFC: SemanticNonNull type (null only on error) ](https://github.com/graphql/graphql-spec/pull/1065). This directive-based approach should allow us to experiment with the concepts and identify issues as we work to understand the viability of semantic nullability in GraphQL. ## Experimental As this is still an experimental implementation, it's designed to be minimally invasive rather than ideal in terms of architecture or performance. As the feature/RFCs stabilize I would imagine we would bake this into the schema crate and data structures as a first class concept. This flag will not be broadly safe to enable in Relay since by default fields that are null due to error are still surfaced to the user. This is only safe to enable if: 1. Your network layer discards all payloads that have any field errors 2. You enable our [explicit error handling feature](https://github.com/facebook/relay/issues/4416), which is still itself experimental. ## Missing Pieces - [ ] Documentation about how to use this feature (purposeful, since this is experimental) - [ ] Support for `semanticNonNullField` which allows a patching an existing type to define it's field's semantic nullability - [ ] Validation - [ ] Invalid use of `levels` will simply panic. - [ ] Uses of `semanticNonNullField` will simply be ignored - [ ] There is no schema validation ensuring interface types have type-compatible semantic nullability Pull Request resolved: https://github.com/facebook/relay/pull/4601 Test Plan: ``` cargo test ``` I also spun up a version of [`grats-relay-example`](https://github.com/captbaritone/grats-relay-example) using Grat's [experimental support for `semanticNonNull`](https://grats.capt.dev/docs/guides/strict-semantic-nullability/) and was able to see it working end to end. https://github.com/facebook/relay/assets/162735/dc979a58-95f3-4e55-9d9b-577afdd798ca Reviewed By: alunyov Differential Revision: D53191255 Pulled By: captbaritone fbshipit-source-id: c09333f2b9475315d81792d33947fd908001c021 --- .../fixtures/argument_definitions.expected | 2 +- ...ragment_with_arguments_defaulting.expected | 2 +- ...fragment-type-suggestions.invalid.expected | 2 +- .../fixtures/client-fields.expected | 12 +- ...tom_scalar_directive_arg_variable.expected | 6 +- .../custom_scalar_variable_arg.expected | 6 +- .../crates/relay-config/src/typegen_config.rs | 13 +++ .../relay-test-schema/src/testschema.graphql | 14 +++ ...inline-fragment-no-type-condition.expected | 4 +- .../client-edge-inline-fragment.expected | 4 +- .../client-edge-to-client-interface.expected | 2 +- .../client-edge-to-client-object.expected | 2 +- .../fixtures/client-edge-variables.expected | 2 +- .../client-edge-with-required.expected | 2 +- ...lient-edge-within-non-client-edge.expected | 2 +- .../fixtures/client-edge.expected | 2 +- ...ested-client-edges-with-variables.expected | 6 +- .../fixtures/nested-client-edges.expected | 6 +- .../fixtures/nested-path-with-alias.expected | 4 +- .../fixtures/nested-path.expected | 4 +- .../fixtures/output-type.expected | 4 +- .../fixtures/field-alias.expected | 2 +- .../fixtures/missing-fragment-name.expected | 2 +- .../multiple-relay-resolvers.expected | 4 +- .../fixtures/nested-relay-resolver.expected | 4 +- ...elay-resolver-backing-client-edge.expected | 2 +- ...lver-field-and-fragment-arguments.expected | 2 +- .../fixtures/relay-resolver-model.expected | 2 +- .../relay-resolver-named-import.expected | 2 +- .../fixtures/relay-resolver-required.expected | 2 +- ...scalar-field-arguments-with-alias.expected | 4 +- ...y-resolver-scalar-field-arguments.expected | 2 +- ...lver-within-named-inline-fragment.expected | 2 +- .../fixtures/relay-resolver.expected | 2 +- compiler/crates/relay-typegen/src/visit.rs | 79 +++++++++---- ...semantic_non_null_in_raw_response.expected | 50 +++++++++ .../semantic_non_null_in_raw_response.graphql | 14 +++ ...semantic_non_null_items_in_matrix.expected | 23 ++++ .../semantic_non_null_items_in_matrix.graphql | 10 ++ ...tic_non_null_liked_field_resolver.expected | 63 +++++++++++ ...ntic_non_null_liked_field_resolver.graphql | 14 +++ ...on_null_liked_field_weak_resolver.expected | 48 ++++++++ ...non_null_liked_field_weak_resolver.graphql | 19 ++++ .../semantic_non_null_linked_field.expected | 27 +++++ .../semantic_non_null_linked_field.graphql | 12 ++ ...antic_non_null_list_and_list_item.expected | 23 ++++ ...mantic_non_null_list_and_list_item.graphql | 10 ++ .../semantic_non_null_list_item.expected | 23 ++++ .../semantic_non_null_list_item.graphql | 10 ++ .../semantic_non_null_scalar.expected | 23 ++++ .../fixtures/semantic_non_null_scalar.graphql | 10 ++ ..._non_null_scalar_feature_disabled.expected | 23 ++++ ...c_non_null_scalar_feature_disabled.graphql | 10 ++ ...semantic_non_null_scalar_required.expected | 23 ++++ .../semantic_non_null_scalar_required.graphql | 10 ++ ...semantic_non_null_scalar_resolver.expected | 29 +++++ .../semantic_non_null_scalar_resolver.graphql | 12 ++ .../relay-typegen/tests/generate_flow/mod.rs | 3 + .../relay-typegen/tests/generate_flow_test.rs | 79 ++++++++++++- ...semantic_non_null_in_raw_response.expected | 50 +++++++++ .../semantic_non_null_in_raw_response.graphql | 14 +++ ...semantic_non_null_items_in_matrix.expected | 21 ++++ .../semantic_non_null_items_in_matrix.graphql | 10 ++ ...tic_non_null_liked_field_resolver.expected | 53 +++++++++ ...ntic_non_null_liked_field_resolver.graphql | 14 +++ ...on_null_liked_field_weak_resolver.expected | 42 +++++++ ...non_null_liked_field_weak_resolver.graphql | 19 ++++ .../semantic_non_null_linked_field.expected | 25 +++++ .../semantic_non_null_linked_field.graphql | 12 ++ ...antic_non_null_list_and_list_item.expected | 21 ++++ ...mantic_non_null_list_and_list_item.graphql | 10 ++ .../semantic_non_null_list_item.expected | 21 ++++ .../semantic_non_null_list_item.graphql | 10 ++ .../semantic_non_null_scalar.expected | 21 ++++ .../fixtures/semantic_non_null_scalar.graphql | 10 ++ ..._non_null_scalar_feature_disabled.expected | 21 ++++ ...c_non_null_scalar_feature_disabled.graphql | 10 ++ ...semantic_non_null_scalar_required.expected | 21 ++++ .../semantic_non_null_scalar_required.graphql | 10 ++ ...semantic_non_null_scalar_resolver.expected | 24 ++++ .../semantic_non_null_scalar_resolver.graphql | 12 ++ .../tests/generate_typescript/mod.rs | 3 + .../tests/generate_typescript_test.rs | 79 ++++++++++++- compiler/crates/schema/src/definitions/mod.rs | 104 ++++++++++++++++++ 84 files changed, 1339 insertions(+), 78 deletions(-) create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.graphql diff --git a/compiler/crates/graphql-ir/tests/parse/fixtures/argument_definitions.expected b/compiler/crates/graphql-ir/tests/parse/fixtures/argument_definitions.expected index f3ca0ad73edee..aabbceb9c061b 100644 --- a/compiler/crates/graphql-ir/tests/parse/fixtures/argument_definitions.expected +++ b/compiler/crates/graphql-ir/tests/parse/fixtures/argument_definitions.expected @@ -87,7 +87,7 @@ fragment TestFragment on User alias: None, definition: WithLocation { location: argument_definitions.graphql:161:171, - item: FieldID(518), + item: FieldID(523), }, arguments: [], directives: [], diff --git a/compiler/crates/graphql-ir/tests/parse/fixtures/fragment_with_arguments_defaulting.expected b/compiler/crates/graphql-ir/tests/parse/fixtures/fragment_with_arguments_defaulting.expected index 2e9b4db4bb856..72ae5f6ceabea 100644 --- a/compiler/crates/graphql-ir/tests/parse/fixtures/fragment_with_arguments_defaulting.expected +++ b/compiler/crates/graphql-ir/tests/parse/fixtures/fragment_with_arguments_defaulting.expected @@ -180,7 +180,7 @@ fragment F2 on Query @argumentDefinitions( alias: None, definition: WithLocation { location: fragment_with_arguments_defaulting.graphql:342:352, - item: FieldID(518), + item: FieldID(523), }, arguments: [], directives: [], diff --git a/compiler/crates/graphql-ir/tests/parse/fixtures/unknown-fragment-type-suggestions.invalid.expected b/compiler/crates/graphql-ir/tests/parse/fixtures/unknown-fragment-type-suggestions.invalid.expected index 832b62d6d4699..ce492bb6975f9 100644 --- a/compiler/crates/graphql-ir/tests/parse/fixtures/unknown-fragment-type-suggestions.invalid.expected +++ b/compiler/crates/graphql-ir/tests/parse/fixtures/unknown-fragment-type-suggestions.invalid.expected @@ -4,7 +4,7 @@ fragment Foo on Users { id } ==================================== ERROR ==================================== -✖︎ Unknown type 'Users'. Did you mean `User` or `Query`? +✖︎ Unknown type 'Users'. Did you mean `User`, `Opera`, or `Query`? unknown-fragment-type-suggestions.invalid.graphql:2:17 1 │ # expected-to-throw diff --git a/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/client-fields.expected b/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/client-fields.expected index c860818a94dbb..056a79395a7a9 100644 --- a/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/client-fields.expected +++ b/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/client-fields.expected @@ -151,7 +151,7 @@ type Foo { alias: None, definition: WithLocation { location: client-fields.graphql:226:238, - item: FieldID(518), + item: FieldID(523), }, arguments: [], directives: [], @@ -228,7 +228,7 @@ type Foo { alias: None, definition: WithLocation { location: client-fields.graphql:367:370, - item: FieldID(519), + item: FieldID(524), }, arguments: [], directives: [], @@ -245,7 +245,7 @@ type Foo { }, InlineFragment { type_condition: Some( - Object(79), + Object(81), ), directives: [], selections: [ @@ -253,7 +253,7 @@ type Foo { alias: None, definition: WithLocation { location: client-fields.graphql:470:472, - item: FieldID(520), + item: FieldID(525), }, arguments: [], directives: [], @@ -279,14 +279,14 @@ type Foo { }, variable_definitions: [], used_global_variables: [], - type_condition: Object(79), + type_condition: Object(81), directives: [], selections: [ ScalarField { alias: None, definition: WithLocation { location: client-fields.graphql:526:528, - item: FieldID(520), + item: FieldID(525), }, arguments: [], directives: [], diff --git a/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_directive_arg_variable.expected b/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_directive_arg_variable.expected index 7e9111a5862ca..806be90100bd7 100644 --- a/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_directive_arg_variable.expected +++ b/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_directive_arg_variable.expected @@ -57,7 +57,7 @@ extend type Query { alias: None, definition: WithLocation { location: custom_scalar_directive_arg_variable.graphql:100:115, - item: FieldID(519), + item: FieldID(524), }, arguments: [], directives: [ @@ -104,7 +104,7 @@ extend type Query { alias: None, definition: WithLocation { location: custom_scalar_directive_arg_variable.graphql:160:170, - item: FieldID(521), + item: FieldID(526), }, arguments: [], directives: [], @@ -115,7 +115,7 @@ extend type Query { alias: None, definition: WithLocation { location: custom_scalar_directive_arg_variable.graphql:181:203, - item: FieldID(520), + item: FieldID(525), }, arguments: [], directives: [ diff --git a/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_variable_arg.expected b/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_variable_arg.expected index c87f60d2b4390..77e9ef2fd4353 100644 --- a/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_variable_arg.expected +++ b/compiler/crates/graphql-ir/tests/parse_with_extensions/fixtures/custom_scalar_variable_arg.expected @@ -55,7 +55,7 @@ extend type Query { alias: None, definition: WithLocation { location: custom_scalar_variable_arg.graphql:100:115, - item: FieldID(519), + item: FieldID(524), }, arguments: [ Argument { @@ -91,7 +91,7 @@ extend type Query { alias: None, definition: WithLocation { location: custom_scalar_variable_arg.graphql:151:161, - item: FieldID(521), + item: FieldID(526), }, arguments: [], directives: [], @@ -102,7 +102,7 @@ extend type Query { alias: None, definition: WithLocation { location: custom_scalar_variable_arg.graphql:172:194, - item: FieldID(520), + item: FieldID(525), }, arguments: [ Argument { diff --git a/compiler/crates/relay-config/src/typegen_config.rs b/compiler/crates/relay-config/src/typegen_config.rs index 9e456fe88d17d..d5c9e79627336 100644 --- a/compiler/crates/relay-config/src/typegen_config.rs +++ b/compiler/crates/relay-config/src/typegen_config.rs @@ -111,6 +111,18 @@ pub struct TypegenConfig { /// of an union with the raw type, null and undefined. #[serde(default)] pub typescript_exclude_undefined_from_nullable_union: bool, + + /// EXPERIMENTAL: If your environment is configured to handles errors out of band, either via + /// a network layer which discards responses with errors, or via enabling strict + /// error handling in the runtime, you can enable this flag to have Relay generate + /// non-null types for fields which are marked as semantically non-null in + /// the schema. + /// + /// Currently semantically non-null fields must be specified in your schema + /// using the `@semanticNonNull` directive as specified in: + /// https://github.com/apollographql/specs/pull/42 + #[serde(default)] + pub experimental_emit_semantic_nullability_types: bool, } impl Default for TypegenConfig { @@ -125,6 +137,7 @@ impl Default for TypegenConfig { no_future_proof_enums: Default::default(), eager_es_modules: Default::default(), typescript_exclude_undefined_from_nullable_union: Default::default(), + experimental_emit_semantic_nullability_types: Default::default(), } } } diff --git a/compiler/crates/relay-test-schema/src/testschema.graphql b/compiler/crates/relay-test-schema/src/testschema.graphql index 9b86454189612..0b04d9ebc46ce 100644 --- a/compiler/crates/relay-test-schema/src/testschema.graphql +++ b/compiler/crates/relay-test-schema/src/testschema.graphql @@ -1191,3 +1191,17 @@ type Settings { type WithWrongViewer { actor_key: Viewer } + +extend type Query { + opera: Opera +} + +type Opera { + composer: User @semanticNonNull + cast: [Portrayal] @semanticNonNull(levels: [0, 1]) +} + +type Portrayal { + singer: User @semanticNonNull + character: String @semanticNonNull +} diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment-no-type-condition.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment-no-type-condition.expected index 1a47a8fe82dea..670b065f6c9b8 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment-no-type-condition.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment-no-type-condition.expected @@ -38,7 +38,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -65,7 +65,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment.expected index 94cdcfacc6557..f4760518b09a2 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-inline-fragment.expected @@ -43,7 +43,7 @@ fragment Foo_node on Node { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -72,7 +72,7 @@ fragment Foo_node on Node { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.expected index c5bfa223c21da..5479c1d082c96 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.expected @@ -42,7 +42,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(520), + # field_id: FieldID(525), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-object.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-object.expected index 7cb0f76795452..6ad756303cbe9 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-object.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-object.expected @@ -39,7 +39,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-variables.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-variables.expected index f2f31d38cc962..88ba45e744bad 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-variables.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-variables.expected @@ -30,7 +30,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected index d03de4a10a327..c3ab346a5ca7d 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected @@ -34,7 +34,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-within-non-client-edge.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-within-non-client-edge.expected index fda59b1bb081f..abba89ab23c21 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-within-non-client-edge.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-within-non-client-edge.expected @@ -33,7 +33,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge.expected index b780eb5659405..81a3d6a1795b1 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge.expected @@ -30,7 +30,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges-with-variables.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges-with-variables.expected index bb12f986c7b98..707f4cb00ae7f 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges-with-variables.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges-with-variables.expected @@ -34,7 +34,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -57,7 +57,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -115,7 +115,7 @@ fragment RefetchableClientEdgeQuery_Foo_user_best_friend on User @__ClientEdgeGe { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges.expected index 7f1e751132de0..ca3992d0e0c6d 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-client-edges.expected @@ -32,7 +32,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -54,7 +54,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -110,7 +110,7 @@ fragment RefetchableClientEdgeQuery_Foo_user_best_friend on User @__ClientEdgeGe { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path-with-alias.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path-with-alias.expected index 2f04201557a8a..53b7b8cadae6e 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path-with-alias.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path-with-alias.expected @@ -45,7 +45,7 @@ fragment Foo_user on ClientUser { { ...BestFriendFragment @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "BestFriendResolver", # import_name: None, # field_alias: Some( @@ -74,7 +74,7 @@ fragment Foo_user on ClientUser { { ...BestFriendFragment @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "BestFriendResolver", # import_name: None, # field_alias: Some( diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path.expected index bc327a9225b01..5b9cb53ca6d86 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/nested-path.expected @@ -45,7 +45,7 @@ fragment Foo_user on ClientUser { { ...BestFriendFragment @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -72,7 +72,7 @@ fragment Foo_user on ClientUser { { ...BestFriendFragment @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/output-type.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/output-type.expected index 5af7114331ff2..80fb31114167a 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/output-type.expected +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/output-type.expected @@ -39,7 +39,7 @@ fragment Foo_user on User { { ...BestFriendResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, @@ -48,7 +48,7 @@ fragment Foo_user on User { # live: false, # output_type_info: Composite( # ResolverNormalizationInfo { - # inner_type: Object(79), + # inner_type: Object(81), # plural: false, # normalization_operation: WithLocation { # location: :59:70, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/field-alias.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/field-alias.expected index 2046ba2ad0bba..822896d5868f8 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/field-alias.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/field-alias.expected @@ -22,7 +22,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: Some( diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/missing-fragment-name.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/missing-fragment-name.expected index b8ec6948dd602..171d4f426afa8 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/missing-fragment-name.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/missing-fragment-name.expected @@ -12,7 +12,7 @@ extend type User { fragment Foo_user on User { __id @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/multiple-relay-resolvers.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/multiple-relay-resolvers.expected index e86a0471bb90a..3aed160e9336c 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/multiple-relay-resolvers.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/multiple-relay-resolvers.expected @@ -28,7 +28,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, @@ -41,7 +41,7 @@ fragment Foo_user on User { ...HobbitNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "HobbitNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/nested-relay-resolver.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/nested-relay-resolver.expected index 8b29673574b47..68962a00356be 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/nested-relay-resolver.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/nested-relay-resolver.expected @@ -28,7 +28,7 @@ extend type User { fragment Foo_user on User { ...HobbitNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(519), + # field_id: FieldID(524), # import_path: "HobbitNameResolver", # import_name: None, # field_alias: None, @@ -45,7 +45,7 @@ fragment HobbitNameResolverFragment_name on User { name ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-backing-client-edge.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-backing-client-edge.expected index 980e270820104..5efc0e36bed07 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-backing-client-edge.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-backing-client-edge.expected @@ -26,7 +26,7 @@ fragment BestFriendResolverFragment on User { fragment Foo_user on User { ...BestFriendResolverFragment @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "BestFriendResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-field-and-fragment-arguments.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-field-and-fragment-arguments.expected index b3389c2c22baa..6298fd55e76a6 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-field-and-fragment-arguments.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-field-and-fragment-arguments.expected @@ -17,7 +17,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-model.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-model.expected index e9e367cecca63..16aa3da751432 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-model.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-model.expected @@ -22,7 +22,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-named-import.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-named-import.expected index 7e9cadc021b01..977c77bc39329 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-named-import.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-named-import.expected @@ -22,7 +22,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: Some( # "pop_star_name", diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-required.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-required.expected index 15eec410aeff7..ebf93821cff02 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-required.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-required.expected @@ -22,7 +22,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments-with-alias.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments-with-alias.expected index 074f4e822e0ad..18569a2f5e7cb 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments-with-alias.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments-with-alias.expected @@ -13,7 +13,7 @@ extend type User { fragment Foo_user on User { __id @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, @@ -43,7 +43,7 @@ fragment Foo_user on User { __id @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: Some( diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments.expected index cffcc99529c43..e2a539499c559 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-scalar-field-arguments.expected @@ -12,7 +12,7 @@ extend type User { fragment Foo_user on User { __id @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-within-named-inline-fragment.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-within-named-inline-fragment.expected index ffd15c08de9b2..454dd973a2543 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-within-named-inline-fragment.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver-within-named-inline-fragment.expected @@ -33,7 +33,7 @@ fragment Foo_user on Node { { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver.expected b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver.expected index 864997cd55364..d4af804be167c 100644 --- a/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver.expected +++ b/compiler/crates/relay-transforms/tests/relay_resolvers/fixtures/relay-resolver.expected @@ -22,7 +22,7 @@ extend type User { fragment Foo_user on User { ...PopStarNameResolverFragment_name @__RelayResolverMetadata # RelayResolverMetadata { - # field_id: FieldID(518), + # field_id: FieldID(523), # import_path: "PopStarNameResolver", # import_name: None, # field_alias: None, diff --git a/compiler/crates/relay-typegen/src/visit.rs b/compiler/crates/relay-typegen/src/visit.rs index e19a1dd6e0773..05092917b21f5 100644 --- a/compiler/crates/relay-typegen/src/visit.rs +++ b/compiler/crates/relay-typegen/src/visit.rs @@ -154,11 +154,11 @@ pub(crate) fn visit_selections( enclosing_linked_field_concrete_type, ), Selection::LinkedField(linked_field) => { - let linked_field_type = typegen_context - .schema - .field(linked_field.definition.item) - .type_ - .inner(); + let linked_field_type = field_type( + typegen_context.schema.field(linked_field.definition.item), + typegen_context, + ) + .inner(); let nested_enclosing_linked_field_concrete_type = if linked_field_type.is_abstract_type() { None @@ -166,7 +166,7 @@ pub(crate) fn visit_selections( Some(linked_field_type) }; gen_visit_linked_field( - typegen_context.schema, + typegen_context, &mut type_selections, linked_field, |selections| { @@ -330,7 +330,7 @@ fn generate_resolver_type( if is_relay_resolver_type(typegen_context, schema_field) { AST::Mixed } else { - let type_ = &schema_field.type_.inner(); + let type_ = &field_type(schema_field, typegen_context).inner(); expect_scalar_type(typegen_context, encountered_enums, custom_scalars, type_) } } @@ -340,22 +340,25 @@ fn generate_resolver_type( Some(normalization_info.normalization_operation.location), ); - if let Some(field_type) = normalization_info.weak_object_instance_field { - let type_ = &typegen_context.schema.field(field_type).type_.inner(); + if let Some(field_id) = normalization_info.weak_object_instance_field { + let type_ = + &field_type(typegen_context.schema.field(field_id), typegen_context).inner(); expect_scalar_type(typegen_context, encountered_enums, custom_scalars, type_) } else { AST::RawType(normalization_info.normalization_operation.item.0) } } ResolverOutputTypeInfo::EdgeTo => create_edge_to_return_type_ast( - &schema_field.type_.inner(), + &field_type(schema_field, typegen_context).inner(), typegen_context.schema, runtime_imports, ), ResolverOutputTypeInfo::Legacy => AST::Mixed, }; - let ast = transform_type_reference_into_ast(&schema_field.type_, |_| inner_ast); + let ast = transform_type_reference_into_ast(&field_type(schema_field, typegen_context), |_| { + inner_ast + }); let return_type = if matches!( typegen_context.project_config.typegen_config.language, @@ -545,11 +548,12 @@ fn relay_resolver_field_type( }; if let Some(field) = maybe_scalar_field { - let inner_value = transform_type_reference_into_ast(&field.type_, |type_| { + let type_ = field_type(field, typegen_context); + let inner_value = transform_type_reference_into_ast(&type_, |type_| { expect_scalar_type(typegen_context, encountered_enums, custom_scalars, type_) }); if required { - if field.type_.is_non_null() { + if type_.is_non_null() { inner_value } else { AST::NonNullable(Box::new(inner_value)) @@ -956,12 +960,12 @@ fn raw_response_visit_inline_fragment( } fn gen_visit_linked_field( - schema: &SDLSchema, + typegen_context: &'_ TypegenContext<'_>, type_selections: &mut Vec, linked_field: &LinkedField, mut visit_selections_fn: impl FnMut(&[Selection]) -> Vec, ) { - let field = schema.field(linked_field.definition.item); + let field = typegen_context.schema.field(linked_field.definition.item); let schema_name = field.name.item; let key = if let Some(alias) = linked_field.alias { alias.item @@ -970,7 +974,10 @@ fn gen_visit_linked_field( }; let selections = visit_selections_fn(&linked_field.selections); - let node_type = apply_required_directive_nullability(&field.type_, &linked_field.directives); + let node_type = apply_required_directive_nullability( + &field_type(field, typegen_context), + &linked_field.directives, + ); type_selections.push(TypeSelection::LinkedField(TypeSelectionLinkedField { field_name_or_alias: key, @@ -996,7 +1003,10 @@ fn visit_scalar_field( } else { schema_name }; - let field_type = apply_required_directive_nullability(&field.type_, &scalar_field.directives); + let field_type = apply_required_directive_nullability( + &field_type(field, typegen_context), + &scalar_field.directives, + ); let special_field = ScalarFieldSpecialSchemaField::from_schema_name( schema_name, &typegen_context.project_config.schema_config, @@ -1919,11 +1929,20 @@ pub(crate) fn raw_response_visit_selections( enclosing_linked_field_concrete_type, ), Selection::LinkedField(linked_field) => { - let linked_field_type = typegen_context - .schema - .field(linked_field.definition.item) - .type_ - .inner(); + // Note: We intentionally use the semantic field type here + // despite the fact that we are generating a raw response type, + // which should model the _server's_ return type. + // + // While it's true that the server may return null for a semantic non-null field, + // it should only do so if that field also has an error in the errors array. Since + // raw response type is generally used to construct payloads for apis which do not + // allow the user to provide additional field level error data, we must ensure that + // only semantically valid values are allowed in the raw response type. + let linked_field_type = field_type( + typegen_context.schema.field(linked_field.definition.item), + typegen_context, + ) + .inner(); let nested_enclosing_linked_field_concrete_type = if linked_field_type.is_abstract_type() { None @@ -1931,7 +1950,7 @@ pub(crate) fn raw_response_visit_selections( Some(linked_field_type) }; gen_visit_linked_field( - typegen_context.schema, + typegen_context, &mut type_selections, linked_field, |selections| { @@ -2408,3 +2427,17 @@ fn return_ast_in_object_case( Type::Interface(_) | Type::Object(_) | Type::Union(_) => ast_in_object_case, } } + +/// Returns the type of the field, potentially wrapping the field or list items in a non-null type +/// to reflect the semantic nullability of the field if that feature is enabled. +fn field_type(field: &Field, typegen_options: &'_ TypegenContext<'_>) -> TypeReference { + if typegen_options + .project_config + .typegen_config + .experimental_emit_semantic_nullability_types + { + field.semantic_type() + } else { + field.type_.clone() + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.expected new file mode 100644 index 0000000000000..86573a72ee9e3 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.expected @@ -0,0 +1,50 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +query MyQuery @raw_response_type { + opera { + composer { + name + } + cast { + singer { + name + } + character + } + } +} +==================================== OUTPUT =================================== +export type MyQuery$variables = {||}; +export type MyQuery$data = {| + +opera: ?{| + +cast: $ReadOnlyArray<{| + +character: string, + +singer: {| + +name: ?string, + |}, + |}>, + +composer: {| + +name: ?string, + |}, + |}, +|}; +export type MyQuery$rawResponse = {| + +opera?: ?{| + +cast: $ReadOnlyArray<{| + +character: string, + +singer: {| + +id: string, + +name: ?string, + |}, + |}>, + +composer: {| + +id: string, + +name: ?string, + |}, + |}, +|}; +export type MyQuery = {| + rawResponse: MyQuery$rawResponse, + response: MyQuery$data, + variables: MyQuery$variables, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.graphql new file mode 100644 index 0000000000000..5ba29f17a3cd0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_in_raw_response.graphql @@ -0,0 +1,14 @@ +# relay:experimental_emit_semantic_nullability_types +query MyQuery @raw_response_type { + opera { + composer { + name + } + cast { + singer { + name + } + character + } + } +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.expected new file mode 100644 index 0000000000000..cd960f95c714f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.expected @@ -0,0 +1,23 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on Screen { + pixels +} + +%extensions% + +type Screen { + pixels: [[Int]] @semanticNonNull(levels: [2]) +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +pixels: ?$ReadOnlyArray>, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.graphql new file mode 100644 index 0000000000000..222ac17db8761 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_items_in_matrix.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on Screen { + pixels +} + +%extensions% + +type Screen { + pixels: [[Int]] @semanticNonNull(levels: [2]) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.expected new file mode 100644 index 0000000000000..0f0e98b341fd3 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.expected @@ -0,0 +1,63 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend @waterfall { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} +==================================== OUTPUT =================================== +import type { RefetchableClientEdgeQuery_MyFragment_best_friend$fragmentType } from "RefetchableClientEdgeQuery_MyFragment_best_friend.graphql"; +export type ClientEdgeQuery_MyFragment_best_friend$variables = {| + id: string, +|}; +export type ClientEdgeQuery_MyFragment_best_friend$data = {| + +node: ?{| + +$fragmentSpreads: RefetchableClientEdgeQuery_MyFragment_best_friend$fragmentType, + |}, +|}; +export type ClientEdgeQuery_MyFragment_best_friend = {| + response: ClientEdgeQuery_MyFragment_best_friend$data, + variables: ClientEdgeQuery_MyFragment_best_friend$variables, +|}; +------------------------------------------------------------------------------- +import type { FragmentType, DataID } from "relay-runtime"; +import clientUserBestFriendResolverType from "bar"; +// Type assertion validating that `clientUserBestFriendResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(clientUserBestFriendResolverType: () => {| + +id: DataID, +|}); +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +best_friend: {| + +name: ?string, + |}, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; +------------------------------------------------------------------------------- +import type { FragmentType } from "relay-runtime"; +declare export opaque type RefetchableClientEdgeQuery_MyFragment_best_friend$fragmentType: FragmentType; +import type { ClientEdgeQuery_MyFragment_best_friend$variables } from "ClientEdgeQuery_MyFragment_best_friend.graphql"; +export type RefetchableClientEdgeQuery_MyFragment_best_friend$data = {| + +id: string, + +name: ?string, + +$fragmentType: RefetchableClientEdgeQuery_MyFragment_best_friend$fragmentType, +|}; +export type RefetchableClientEdgeQuery_MyFragment_best_friend$key = { + +$data?: RefetchableClientEdgeQuery_MyFragment_best_friend$data, + +$fragmentSpreads: RefetchableClientEdgeQuery_MyFragment_best_friend$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.graphql new file mode 100644 index 0000000000000..66e1961352504 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_resolver.graphql @@ -0,0 +1,14 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend @waterfall { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.expected new file mode 100644 index 0000000000000..ef1fce01ed98e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.expected @@ -0,0 +1,48 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + blob { + data + } +} + +%extensions% + +type ClientUser { + blob: Blob @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + has_output_type: true + ) +} + +type Blob { + data: String +} +==================================== OUTPUT =================================== +export type ClientUser__blob$normalization$variables = {||}; +export type ClientUser__blob$normalization$data = {| + +data: ?string, +|}; +export type ClientUser__blob$normalization = {| + response: ClientUser__blob$normalization$data, + variables: ClientUser__blob$normalization$variables, +|}; +------------------------------------------------------------------------------- +import type { ClientUser__blob$normalization } from "ClientUser__blob$normalization.graphql"; +import type { FragmentType } from "relay-runtime"; +import clientUserBlobResolverType from "bar"; +// Type assertion validating that `clientUserBlobResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(clientUserBlobResolverType: () => ClientUser__blob$normalization); +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +blob: {| + +data: ?string, + |}, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.graphql new file mode 100644 index 0000000000000..8de6291d2973f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.graphql @@ -0,0 +1,19 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + blob { + data + } +} + +%extensions% + +type ClientUser { + blob: Blob @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + has_output_type: true + ) +} + +type Blob { + data: String +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.expected new file mode 100644 index 0000000000000..4ec2c524e3aee --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.expected @@ -0,0 +1,27 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +best_friend: {| + +name: ?string, + |}, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.graphql new file mode 100644 index 0000000000000..08868d5dcda7f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_linked_field.graphql @@ -0,0 +1,12 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.expected new file mode 100644 index 0000000000000..d5c688313f577 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.expected @@ -0,0 +1,23 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [0, 1]) +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +favorite_numbers: $ReadOnlyArray, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.graphql new file mode 100644 index 0000000000000..5184715697faf --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_and_list_item.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [0, 1]) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.expected new file mode 100644 index 0000000000000..c83a41501c8cd --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.expected @@ -0,0 +1,23 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [1]) +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +favorite_numbers: ?$ReadOnlyArray, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.graphql new file mode 100644 index 0000000000000..cfe1dd7583c0f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_list_item.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [1]) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.expected new file mode 100644 index 0000000000000..1e6706d1ef73f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.expected @@ -0,0 +1,23 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +name: string, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.graphql new file mode 100644 index 0000000000000..9222bcd1c4332 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.expected new file mode 100644 index 0000000000000..a5f0034010bc3 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.expected @@ -0,0 +1,23 @@ +==================================== INPUT ==================================== +# Note: No comment here enabling `experimental_emit_semantic_nullability_types` +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +name: ?string, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.graphql new file mode 100644 index 0000000000000..be10a224f6cec --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.graphql @@ -0,0 +1,10 @@ +# Note: No comment here enabling `experimental_emit_semantic_nullability_types` +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.expected new file mode 100644 index 0000000000000..08a1c2be0d443 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.expected @@ -0,0 +1,23 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name @required(action: LOG) +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = ?{| + +name: string, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.graphql new file mode 100644 index 0000000000000..717fde11aad8f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_required.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name @required(action: LOG) +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.expected new file mode 100644 index 0000000000000..6f604bcb69420 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.expected @@ -0,0 +1,29 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +import clientUserNameResolverType from "bar"; +// Type assertion validating that `clientUserNameResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(clientUserNameResolverType: () => mixed); +declare export opaque type MyFragment$fragmentType: FragmentType; +export type MyFragment$data = {| + +name: ?ReturnType, + +$fragmentType: MyFragment$fragmentType, +|}; +export type MyFragment$key = { + +$data?: MyFragment$data, + +$fragmentSpreads: MyFragment$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.graphql new file mode 100644 index 0000000000000..fdd6196c47966 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/semantic_non_null_scalar_resolver.graphql @@ -0,0 +1,12 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_flow/mod.rs b/compiler/crates/relay-typegen/tests/generate_flow/mod.rs index 9f52fdc4c2ba5..5f0b7d132b87d 100644 --- a/compiler/crates/relay-typegen/tests/generate_flow/mod.rs +++ b/compiler/crates/relay-typegen/tests/generate_flow/mod.rs @@ -92,6 +92,9 @@ pub async fn transform_fixture(fixture: &Fixture<'_>) -> Result typegen_config: TypegenConfig { language: TypegenLanguage::Flow, custom_scalar_types, + experimental_emit_semantic_nullability_types: fixture + .content + .contains("# relay:experimental_emit_semantic_nullability_types"), ..Default::default() }, ..Default::default() diff --git a/compiler/crates/relay-typegen/tests/generate_flow_test.rs b/compiler/crates/relay-typegen/tests/generate_flow_test.rs index 2b3a91b7a8405..b9d7b430e0cae 100644 --- a/compiler/crates/relay-typegen/tests/generate_flow_test.rs +++ b/compiler/crates/relay-typegen/tests/generate_flow_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0785113b958651724c9f7105d4f66b9a>> */ mod generate_flow; @@ -691,6 +691,83 @@ async fn scalar_field() { test_fixture(transform_fixture, file!(), "scalar-field.graphql", "generate_flow/fixtures/scalar-field.expected", input, expected).await; } +#[tokio::test] +async fn semantic_non_null_in_raw_response() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_in_raw_response.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_in_raw_response.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_in_raw_response.graphql", "generate_flow/fixtures/semantic_non_null_in_raw_response.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_items_in_matrix() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_items_in_matrix.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_items_in_matrix.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_items_in_matrix.graphql", "generate_flow/fixtures/semantic_non_null_items_in_matrix.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_liked_field_resolver() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_liked_field_resolver.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_liked_field_resolver.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_liked_field_resolver.graphql", "generate_flow/fixtures/semantic_non_null_liked_field_resolver.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_liked_field_weak_resolver() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_liked_field_weak_resolver.graphql", "generate_flow/fixtures/semantic_non_null_liked_field_weak_resolver.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_linked_field() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_linked_field.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_linked_field.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_linked_field.graphql", "generate_flow/fixtures/semantic_non_null_linked_field.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_list_and_list_item() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_list_and_list_item.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_list_and_list_item.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_list_and_list_item.graphql", "generate_flow/fixtures/semantic_non_null_list_and_list_item.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_list_item() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_list_item.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_list_item.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_list_item.graphql", "generate_flow/fixtures/semantic_non_null_list_item.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_scalar.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_scalar.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar.graphql", "generate_flow/fixtures/semantic_non_null_scalar.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar_feature_disabled() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_feature_disabled.graphql", "generate_flow/fixtures/semantic_non_null_scalar_feature_disabled.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar_required() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_scalar_required.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_scalar_required.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_required.graphql", "generate_flow/fixtures/semantic_non_null_scalar_required.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar_resolver() { + let input = include_str!("generate_flow/fixtures/semantic_non_null_scalar_resolver.graphql"); + let expected = include_str!("generate_flow/fixtures/semantic_non_null_scalar_resolver.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_resolver.graphql", "generate_flow/fixtures/semantic_non_null_scalar_resolver.expected", input, expected).await; +} + #[tokio::test] async fn simple() { let input = include_str!("generate_flow/fixtures/simple.graphql"); diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.expected new file mode 100644 index 0000000000000..2c0e5630fb941 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.expected @@ -0,0 +1,50 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +query MyQuery @raw_response_type { + opera { + composer { + name + } + cast { + singer { + name + } + character + } + } +} +==================================== OUTPUT =================================== +export type MyQuery$variables = Record; +export type MyQuery$data = { + readonly opera: { + readonly cast: ReadonlyArray<{ + readonly character: string; + readonly singer: { + readonly name: string | null | undefined; + }; + }>; + readonly composer: { + readonly name: string | null | undefined; + }; + } | null | undefined; +}; +export type MyQuery$rawResponse = { + readonly opera?: { + readonly cast: ReadonlyArray<{ + readonly character: string; + readonly singer: { + readonly id: string; + readonly name: string | null | undefined; + }; + }>; + readonly composer: { + readonly id: string; + readonly name: string | null | undefined; + }; + } | null | undefined; +}; +export type MyQuery = { + rawResponse: MyQuery$rawResponse; + response: MyQuery$data; + variables: MyQuery$variables; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.graphql new file mode 100644 index 0000000000000..5ba29f17a3cd0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_in_raw_response.graphql @@ -0,0 +1,14 @@ +# relay:experimental_emit_semantic_nullability_types +query MyQuery @raw_response_type { + opera { + composer { + name + } + cast { + singer { + name + } + character + } + } +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.expected new file mode 100644 index 0000000000000..a271d1ceadc80 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on Screen { + pixels +} + +%extensions% + +type Screen { + pixels: [[Int]] @semanticNonNull(levels: [2]) +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly pixels: ReadonlyArray | null | undefined> | null | undefined; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.graphql new file mode 100644 index 0000000000000..222ac17db8761 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_items_in_matrix.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on Screen { + pixels +} + +%extensions% + +type Screen { + pixels: [[Int]] @semanticNonNull(levels: [2]) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.expected new file mode 100644 index 0000000000000..1cbb9fec31a19 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.expected @@ -0,0 +1,53 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend @waterfall { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type ClientEdgeQuery_MyFragment_best_friend$variables = { + id: string; +}; +export type ClientEdgeQuery_MyFragment_best_friend$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"RefetchableClientEdgeQuery_MyFragment_best_friend">; + } | null | undefined; +}; +export type ClientEdgeQuery_MyFragment_best_friend = { + response: ClientEdgeQuery_MyFragment_best_friend$data; + variables: ClientEdgeQuery_MyFragment_best_friend$variables; +}; +------------------------------------------------------------------------------- +import { FragmentRefs, DataID } from "relay-runtime"; +import clientUserBestFriendResolverType from "bar"; +export type MyFragment$data = { + readonly best_friend: { + readonly name: string | null | undefined; + }; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; +------------------------------------------------------------------------------- +import { FragmentRefs } from "relay-runtime"; +export type RefetchableClientEdgeQuery_MyFragment_best_friend$data = { + readonly id: string; + readonly name: string | null | undefined; + readonly " $fragmentType": "RefetchableClientEdgeQuery_MyFragment_best_friend"; +}; +export type RefetchableClientEdgeQuery_MyFragment_best_friend$key = { + readonly " $data"?: RefetchableClientEdgeQuery_MyFragment_best_friend$data; + readonly " $fragmentSpreads": FragmentRefs<"RefetchableClientEdgeQuery_MyFragment_best_friend">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.graphql new file mode 100644 index 0000000000000..66e1961352504 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_resolver.graphql @@ -0,0 +1,14 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend @waterfall { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.expected new file mode 100644 index 0000000000000..b20d90ad8efc7 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.expected @@ -0,0 +1,42 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + blob { + data + } +} + +%extensions% + +type ClientUser { + blob: Blob @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + has_output_type: true + ) +} + +type Blob { + data: String +} +==================================== OUTPUT =================================== +export type ClientUser__blob$normalization$variables = Record; +export type ClientUser__blob$normalization$data = { + readonly data: string | null | undefined; +}; +export type ClientUser__blob$normalization = { + response: ClientUser__blob$normalization$data; + variables: ClientUser__blob$normalization$variables; +}; +------------------------------------------------------------------------------- +import { FragmentRefs } from "relay-runtime"; +import clientUserBlobResolverType from "bar"; +export type MyFragment$data = { + readonly blob: { + readonly data: string | null | undefined; + }; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.graphql new file mode 100644 index 0000000000000..8de6291d2973f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.graphql @@ -0,0 +1,19 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + blob { + data + } +} + +%extensions% + +type ClientUser { + blob: Blob @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + has_output_type: true + ) +} + +type Blob { + data: String +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.expected new file mode 100644 index 0000000000000..4c9a3bcb2feb4 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.expected @@ -0,0 +1,25 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly best_friend: { + readonly name: string | null | undefined; + }; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.graphql new file mode 100644 index 0000000000000..08868d5dcda7f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_linked_field.graphql @@ -0,0 +1,12 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + best_friend { + name + } +} + +%extensions% + +type ClientUser { + best_friend: User @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.expected new file mode 100644 index 0000000000000..ca0dd00dbcfb9 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [0, 1]) +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly favorite_numbers: ReadonlyArray; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.graphql new file mode 100644 index 0000000000000..5184715697faf --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_and_list_item.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [0, 1]) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.expected new file mode 100644 index 0000000000000..62dc50cbe1f69 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [1]) +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly favorite_numbers: ReadonlyArray | null | undefined; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.graphql new file mode 100644 index 0000000000000..cfe1dd7583c0f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_list_item.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + favorite_numbers +} + +%extensions% + +type ClientUser { + favorite_numbers: [Int] @semanticNonNull(levels: [1]) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.expected new file mode 100644 index 0000000000000..d6c915cf38d57 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly name: string; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.graphql new file mode 100644 index 0000000000000..9222bcd1c4332 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.expected new file mode 100644 index 0000000000000..5e41d0946e1e4 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +# Note: No comment here enabling `experimental_emit_semantic_nullability_types` +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly name: string | null | undefined; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.graphql new file mode 100644 index 0000000000000..be10a224f6cec --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.graphql @@ -0,0 +1,10 @@ +# Note: No comment here enabling `experimental_emit_semantic_nullability_types` +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.expected new file mode 100644 index 0000000000000..00d6da301dd67 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name @required(action: LOG) +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly name: string; + readonly " $fragmentType": "MyFragment"; +} | null | undefined; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.graphql new file mode 100644 index 0000000000000..717fde11aad8f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_required.graphql @@ -0,0 +1,10 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name @required(action: LOG) +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.expected new file mode 100644 index 0000000000000..f482279117ec0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.expected @@ -0,0 +1,24 @@ +==================================== INPUT ==================================== +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +import clientUserNameResolverType from "bar"; +export type MyFragment$data = { + readonly name: ReturnType | null | undefined; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.graphql new file mode 100644 index 0000000000000..fdd6196c47966 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic_non_null_scalar_resolver.graphql @@ -0,0 +1,12 @@ +# relay:experimental_emit_semantic_nullability_types +fragment MyFragment on ClientUser { + name +} + +%extensions% + +type ClientUser { + name: String @semanticNonNull @relay_resolver( + import_path: "./foo/bar.js" + ) +} \ No newline at end of file diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs b/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs index a71b267912cc5..3252ba944a62f 100644 --- a/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs +++ b/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs @@ -72,6 +72,9 @@ pub async fn transform_fixture(fixture: &Fixture<'_>) -> Result use_import_type_syntax: fixture .content .contains("# typegen_config.use_import_type_syntax = true"), + experimental_emit_semantic_nullability_types: fixture + .content + .contains("# relay:experimental_emit_semantic_nullability_types"), ..Default::default() }, feature_flags: Arc::new(FeatureFlags { diff --git a/compiler/crates/relay-typegen/tests/generate_typescript_test.rs b/compiler/crates/relay-typegen/tests/generate_typescript_test.rs index 8614cd8040700..9ba9006fd9ff4 100644 --- a/compiler/crates/relay-typegen/tests/generate_typescript_test.rs +++ b/compiler/crates/relay-typegen/tests/generate_typescript_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7e6fc749e15ff3be636ff1f6b838a03b>> + * @generated SignedSource<> */ mod generate_typescript; @@ -397,6 +397,83 @@ async fn scalar_field() { test_fixture(transform_fixture, file!(), "scalar-field.graphql", "generate_typescript/fixtures/scalar-field.expected", input, expected).await; } +#[tokio::test] +async fn semantic_non_null_in_raw_response() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_in_raw_response.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_in_raw_response.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_in_raw_response.graphql", "generate_typescript/fixtures/semantic_non_null_in_raw_response.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_items_in_matrix() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_items_in_matrix.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_items_in_matrix.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_items_in_matrix.graphql", "generate_typescript/fixtures/semantic_non_null_items_in_matrix.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_liked_field_resolver() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_liked_field_resolver.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_liked_field_resolver.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_liked_field_resolver.graphql", "generate_typescript/fixtures/semantic_non_null_liked_field_resolver.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_liked_field_weak_resolver() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_liked_field_weak_resolver.graphql", "generate_typescript/fixtures/semantic_non_null_liked_field_weak_resolver.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_linked_field() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_linked_field.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_linked_field.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_linked_field.graphql", "generate_typescript/fixtures/semantic_non_null_linked_field.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_list_and_list_item() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_list_and_list_item.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_list_and_list_item.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_list_and_list_item.graphql", "generate_typescript/fixtures/semantic_non_null_list_and_list_item.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_list_item() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_list_item.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_list_item.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_list_item.graphql", "generate_typescript/fixtures/semantic_non_null_list_item.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_scalar.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_scalar.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar.graphql", "generate_typescript/fixtures/semantic_non_null_scalar.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar_feature_disabled() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_feature_disabled.graphql", "generate_typescript/fixtures/semantic_non_null_scalar_feature_disabled.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar_required() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_scalar_required.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_scalar_required.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_required.graphql", "generate_typescript/fixtures/semantic_non_null_scalar_required.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_scalar_resolver() { + let input = include_str!("generate_typescript/fixtures/semantic_non_null_scalar_resolver.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic_non_null_scalar_resolver.expected"); + test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_resolver.graphql", "generate_typescript/fixtures/semantic_non_null_scalar_resolver.expected", input, expected).await; +} + #[tokio::test] async fn simple() { let input = include_str!("generate_typescript/fixtures/simple.graphql"); diff --git a/compiler/crates/schema/src/definitions/mod.rs b/compiler/crates/schema/src/definitions/mod.rs index 4e40e149e0bfa..aecd3c79a27cf 100644 --- a/compiler/crates/schema/src/definitions/mod.rs +++ b/compiler/crates/schema/src/definitions/mod.rs @@ -221,6 +221,34 @@ impl TypeReference { } } + // Given a multi-dimensional list type, return a new type where the level'th nested + // list is non-null. + pub fn with_non_null_level(&self, level: i64) -> TypeReference { + match self { + TypeReference::Named(_) => { + if level == 0 { + self.non_null() + } else { + panic!("Invalid level {} for Named type", level) + } + } + TypeReference::List(of) => { + if level == 0 { + self.non_null() + } else { + TypeReference::List(Box::new(of.with_non_null_level(level - 1))) + } + } + TypeReference::NonNull(of) => { + if level == 0 { + panic!("Invalid level {} for NonNull type", level) + } else { + TypeReference::NonNull(Box::new(of.with_non_null_level(level))) + } + } + } + } + // If the type is Named or NonNull return the inner named. // If the type is a List or NonNull returns a matching list with nullable items. pub fn with_nullable_item_type(&self) -> TypeReference { @@ -252,6 +280,40 @@ impl TypeReference { } } +// Tests for TypeReference::with_non_null_level +#[test] +fn test_with_non_null_level() { + let matrix = TypeReference::List(Box::new(TypeReference::List(Box::new( + TypeReference::Named(Type::Scalar(ScalarID(0))), + )))); + + assert_eq!( + matrix.with_non_null_level(0), + TypeReference::NonNull(Box::new(TypeReference::List(Box::new( + TypeReference::List(Box::new(TypeReference::Named(Type::Scalar(ScalarID(0))))) + )))) + ); + + assert_eq!( + matrix.with_non_null_level(1), + TypeReference::List(Box::new(TypeReference::NonNull(Box::new( + TypeReference::List(Box::new(TypeReference::Named(Type::Scalar(ScalarID(0))))) + )))) + ); + + assert_eq!( + matrix.with_non_null_level(0), + TypeReference::NonNull(Box::new(TypeReference::List(Box::new( + TypeReference::List(Box::new(TypeReference::Named(Type::Scalar(ScalarID(0))))) + )))) + ); + + assert_eq!( + TypeReference::Named(Type::Scalar(ScalarID(0))).with_non_null_level(0), + TypeReference::NonNull(Box::new(TypeReference::Named(Type::Scalar(ScalarID(0))))), + ); +} + impl TypeReference { pub fn map(self, transform: impl FnOnce(T) -> U) -> TypeReference { match self { @@ -395,6 +457,30 @@ impl Field { .and_then(|reason| reason.value.get_string_literal()), }) } + pub fn semantic_type(&self) -> TypeReference { + match self + .directives + .named(DirectiveName("semanticNonNull".intern())) + { + Some(directive) => { + match directive + .arguments + .named(ArgumentName("levels".intern())) + .map(|levels| levels.expect_int_list()) + { + Some(levels) => { + let mut type_ = self.type_.clone(); + for level in levels { + type_ = type_.with_non_null_level(level); + } + type_ + } + None => self.type_.non_null(), + } + } + None => self.type_.clone(), + } + } } #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -448,6 +534,24 @@ impl ArgumentValue { panic!("expected a string literal, got {:?}", self); }) } + /// Return the constant string literal of this value. + /// Panics if the value is not a constant string literal. + pub fn expect_int_list(&self) -> Vec { + if let ConstantValue::List(list) = &self.value { + list.items + .iter() + .map(|item| { + if let ConstantValue::Int(int) = item { + int.value + } else { + panic!("expected a int literal, got {:?}", item); + } + }) + .collect() + } else { + panic!("expected a list, got {:?}", self); + } + } } #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]