facebook/relay · error

Unexpected PropertyAccessor

Error message

Unexpected PropertyAccessor

What it means

Primitive::PropertyAccessor is used when printing values that must be read from runtime variables (e.g. `$variables.key` accessors) in generated code paths that support it. write_constant_value only emits static constants, so a PropertyAccessor appearing there means a runtime-dependent value leaked into a constant position and the printer panics instead of emitting invalid literal code.

Source

Thrown at compiler/crates/relay-codegen/src/printer.rs:935

                    }
                    if !obj.is_empty() {
                        f.pop();
                    }
                    f.push('}');
                    Ok(())
                }
            }
        }
        Primitive::Null | Primitive::SkippableNull => {
            f.push_str("null");
            Ok(())
        }
        Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
        Primitive::RawString(_) => panic!("Unexpected RawString"),
        Primitive::GraphQLModuleDependency(_) => panic!("Unexpected GraphQLModuleDependency"),
        Primitive::JSModuleDependency { .. } => panic!("Unexpected JSModuleDependency"),
        Primitive::ResolverModuleReference { .. } => panic!("Unexpected ResolverModuleReference"),
        Primitive::PropertyAccessor(_) => panic!("Unexpected PropertyAccessor"),
        Primitive::DynamicImport { .. } => panic!("Unexpected DynamicImport"),
        Primitive::RelayResolverModel { .. } => panic!("Unexpected RelayResolver"),
    }
}

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Remove variables from argument positions that must be constant (e.g. @static_arg values); use literal values and pass dynamic data at execution time
  2. Ensure the variable-containing argument is printed via the variables-handling path (which filters Variables before constant printing) rather than nested inside constant values
  3. Inspect the custom transforms for mislabeled dynamic values and emit PropertyAccessor only in accessor-supported writers
  4. Upgrade the Relay compiler so variable filtering matches the printer's expectations

Example fix

// before
...Frag @static_arg(key: "id", value: $dynamicId)
// after
...Frag @static_arg(key: "id", value: "static-id")  // pass dynamicId at runtime instead
Defensive patterns

Strategy: validation

Validate before calling

function assertStaticStaticArgs(doc) {
  visit(doc, {
    Directive(node) {
      if (node.name.value === 'static_arg') {
        node.arguments?.forEach(a => {
          if (containsVariable(a.value)) {
            throw new Error(`@static_arg value for '${a.name.value}' must not contain variables`);
          }
        });
      }
    }
  });
}

Type guard

const isStaticValue = (v) => v.kind !== 'Variable' && !(v.values ?? []).some(isStaticValue === undefined ? false : containsVar(v));

Try / catch

try {
  generateArtifacts();
} catch (e) {
  if (String(e).includes('Unexpected PropertyAccessor')) {
    // replace variable-dependent values in constant positions with literals
  }
  throw e;
}

Prevention

When it happens

Trigger: A value dependent on query variables (yielding a PropertyAccessor primitive, typically after Variables-in-argument handling) reaching write_constant_value — e.g. variable-derived arguments printed in a context that only supports constants, such as nested constant printing in write_argument_value.

Common situations: Arguments containing variables in positions codegen expects to be fully static (e.g. @static_arg values, storage-key embedded values with nested variables); custom transforms mislabeling dynamic values; compiler upgrades changing which positions accept variables.

Related errors


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/a0c1ce4399356a06. Report an issue: GitHub.