{"record":{"id":"2a45325b35ae1d5f","repo":"facebook/relay","slug":"expected-field-to-be-defined-on-concrete-type","errorCode":null,"errorMessage":"Expected field to be defined on concrete type","messagePattern":"Expected field to be defined on concrete type","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-transforms/src/relay_resolvers/fragment_dependencies.rs","lineNumber":109,"sourceCode":"        })\n        .and_then(|arg| arg.value.get_string_literal().map(FragmentDefinitionName));\n\n    if let Some(name) = name {\n        return vec![name];\n    }\n\n    let mut fragment_names = Vec::new();\n    // For a resolver field on an abstract type, we currently need to include rootFragments for all implementations,\n    // because compiling resolvers don't have incremental mode\n    if let Some(Type::Interface(interface_type)) = field.parent_type {\n        let interface = schema.interface(interface_type);\n        let implementing_objects = interface.recursively_implementing_objects(schema);\n\n        // Collect all fragment names from concrete implementations\n        for object_id in implementing_objects.iter() {\n            let concrete_field_id = schema\n                .named_field(Type::Object(*object_id), field.name.item)\n                .expect(\"Expected field to be defined on concrete type\");\n            let concrete_field = schema.field(concrete_field_id);\n\n            if let Some(fragment_name) = concrete_field\n                .directives\n                .named(*RELAY_RESOLVER_DIRECTIVE_NAME)\n                .filter(|resolver_directive| {\n                    let generated = resolver_directive\n                        .arguments\n                        .named(*GENERATED_FRAGMENT_ARGUMENT_NAME)\n                        .and_then(|arg| arg.value.get_bool_literal())\n                        .unwrap_or(false);\n                    !generated\n                })\n                .and_then(|resolver_directive| {\n                    resolver_directive\n                        .arguments\n                        .named(*FRAGMENT_KEY_ARGUMENT_NAME)\n                })","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-transforms/src/relay_resolvers/fragment_dependencies.rs#L91-L127","documentation":"This panic fires while collecting fragment dependencies of a @relay_resolver defined on an interface. For each object implementing the interface, the transform looks up the interface's field on the concrete object; if any implementing object lacks that field, the lookup returns None and the transform panics. Relay requires every implementing object to define the field so dependency fragments can be gathered per concrete type.","triggerScenarios":"An interface (or object spreading an interface fragment) declares a @relay_resolver field, but one or more objects implementing that interface do not define that field in their own selection set / type definition — detected during visit_selections or get_ir_definition_references.","commonSituations":"Adding a @relay_resolver field to an interface and forgetting to add it to some implementing type; a new object type implementing the interface without updating its fields; codegen regenerating types after the interface changed.","solutions":["Add the missing @relay_resolver field to every type that implements the interface.","Run graphql validation (fragment spread/type refinement validation) before the transform so mismatched interface fields are caught earlier.","Regenerate concrete types from the updated interface definitions if types are generated.","Temporarily narrow the interface (remove non-conforming implementors) until all implementors define the field."],"exampleFix":"// before\ninterface Node { image_url: String @relay_resolver(import_path: \"...\", fragment_name: \"...\") }\n// after\ntype User implements Node { image_url: String @relay_resolver(...) } // every implementor defines the field","handlingStrategy":"validation","validationCode":"// Before compiling, check every implementor defines the interface's resolver field:\nfor object_id in interface.recursively_implementing_objects(schema).iter() {\n    assert!(\n        schema.named_field(Type::Object(*object_id), field.name.item).is_some(),\n        \"type {} does not define resolver field {}\",\n        schema.object(*object_id).name.item.0, field.name.item.0\n    );\n}","typeGuard":"fn interface_field_defined_on_all_implementors(schema: &SDLSchema, interface: &Interface, field_name: &str) -> bool {\n    interface.recursively_implementing_objects(schema).iter().all(|id| {\n        schema.named_field(Type::Object(*id), field_name).is_some()\n    })\n}","tryCatchPattern":"// Panics abort the compiler process; validate ahead instead. If embedding the compiler, isolate it:\nlet output = std::panic::catch_unwind(|| run_relay_compiler(config));\nmatch output {\n    Ok(result) => result,\n    Err(_) => Err(\"compiler panicked: interface resolver field missing on an implementor\"),\n}","preventionTips":["Whenever you add a @relay_resolver field to an interface, grep all 'implements <Interface>' types and add the field","Add a CI lint that diffs interface fields against implementing object fields","Regenerate concrete types after interface changes before compiling","Keep fragment/type definitions in the same repo so renames are caught by the compiler"],"tags":["relay","graphql","interface","compiler-transform","panic"],"backgroundTag":"interface-field-missing-on-implementor","analyzedSha":"668b1b85e06261aa3b58dabfc51f8b5524a70955","analyzedAt":"2026-09-02T19:57:20.783Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}