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)]