{"record":{"id":"c9917f4d2f78e888","repo":"zed-industries/zed","slug":"operands-of-must-be-identifiers","errorCode":null,"errorMessage":"operands of == must be identifiers","messagePattern":"operands of == must be identifiers","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gpui/src/keymap/context.rs","lineNumber":435,"sourceCode":"    }\n\n    fn new_or(self, other: Self) -> Result<Self> {\n        Ok(Self::Or(Box::new(self), Box::new(other)))\n    }\n\n    fn new_and(self, other: Self) -> Result<Self> {\n        Ok(Self::And(Box::new(self), Box::new(other)))\n    }\n\n    fn new_child(self, other: Self) -> Result<Self> {\n        Ok(Self::Descendant(Box::new(self), Box::new(other)))\n    }\n\n    fn new_eq(self, other: Self) -> Result<Self> {\n        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {\n            Ok(Self::Equal(left, right))\n        } else {\n            anyhow::bail!(\"operands of == must be identifiers\");\n        }\n    }\n\n    fn new_neq(self, other: Self) -> Result<Self> {\n        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {\n            Ok(Self::NotEqual(left, right))\n        } else {\n            anyhow::bail!(\"operands of != must be identifiers\");\n        }\n    }\n\n    fn fmt_joined(\n        &self,\n        f: &mut fmt::Formatter<'_>,\n        separator: &str,\n        operator: LogicalOperator,\n        needs_parens: impl Fn(&Self) -> bool + Copy,\n    ) -> fmt::Result {","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/gpui/src/keymap/context.rs#L417-L453","documentation":"new_eq builds an Equal predicate only when both operands parsed as plain Identifiers — the only shape evaluation supports (it resolves the left identifier's value and compares it to the right). If either side is a composite (parenthesized group, &&/|| chain, '>' descendant, or negation), the constructor bails. Comparisons in this DSL are always identifier-to-identifier.","triggerScenarios":"Writing '(a && b) == c', 'mode == (normal || visual)', or 'A > B == c' — any == whose left or right operand is not a single identifier.","commonSituations":"Trying to compare against a group of alternatives on the right-hand side; adding precedence parentheses around one side of a comparison; copying boolean expression shapes from other keymap formats.","solutions":["Keep both sides of == as bare identifiers: mode == visual","Expand alternatives into a chain: (mode == normal || mode == visual) instead of mode == (normal || visual)","Move composite logic outside the comparison and combine results with && or ||"],"exampleFix":"// before (keymap.json)\n\"context\": \"mode == (normal || visual)\"\n\n// after\n\"context\": \"(mode == normal || mode == visual)\"","handlingStrategy":"validation","validationCode":"KeyBindingContextPredicate::parse(\"(mode == normal || mode == visual)\")?; // validate before install","typeGuard":"fn is_parseable_context(source: &str) -> bool {\n    KeyBindingContextPredicate::parse(source).is_ok()\n}","tryCatchPattern":null,"preventionTips":["Remember == and != combine identifiers only","Expand alternatives into || chains of whole comparisons","Parenthesize composite operands away from comparisons"],"tags":["gpui","keymap","parsing","keyboard-shortcuts","dsl"],"backgroundTag":"keymap-parse-error","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}