{"record":{"id":"3d656094c3ccd507","repo":"astral-sh/ruff","slug":"failed-to-fix-invalid-comparison-node","errorCode":null,"errorMessage":"Failed to fix invalid comparison: {node:?}","messagePattern":"Failed to fix invalid comparison: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs","lineNumber":102,"sourceCode":"                || helpers::is_constant_non_singleton(right)\n                || helpers::is_mutable_iterable_initializer(left)\n                || helpers::is_mutable_iterable_initializer(right))\n        {\n            let mut diagnostic =\n                checker.report_diagnostic(IsLiteral { cmp_op: op.into() }, compare.range());\n            if lazy_located.is_none() {\n                lazy_located = Some(locate_cmp_ops(compare.range(), checker.tokens()));\n            }\n            diagnostic.try_set_optional_fix(|| {\n                if let Some(located_op) =\n                    lazy_located.as_ref().and_then(|located| located.get(index))\n                {\n                    assert_eq!(located_op.op, *op);\n                    if let Ok(content) = match located_op.op {\n                        CmpOp::Is => Ok::<String, Error>(\"==\".to_string()),\n                        CmpOp::IsNot => Ok(\"!=\".to_string()),\n                        node => {\n                            bail!(\"Failed to fix invalid comparison: {node:?}\")\n                        }\n                    } {\n                        Ok(Some(Fix::safe_edit(Edit::range_replacement(\n                            content,\n                            located_op.range,\n                        ))))\n                    } else {\n                        Ok(None)\n                    }\n                } else {\n                    bail!(\"Failed to fix invalid comparison due to missing op\")\n                }\n            });\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Copy, Clone)]","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs#L84-L120","documentation":"Ruff's F632 (is-literal comparison) autofix replaces `is`/`is not` with `==`/`!=` for literal comparisons. This bail fires when the located comparison operator node is neither `Is` nor `IsNot`, meaning the cached operator no longer matches the fix's expectation, so no safe replacement exists.","triggerScenarios":"`invalid_literal_comparison` recorded an operator range, then while building the fix the `CmpOp` at that node matched neither `CmpOp::Is` nor `CmpOp::IsNot` — an internal invariant violation between diagnostic collection and fix generation.","commonSituations":"Effectively unreachable in released versions; may appear with patched/modified Ruff builds or mid-version AST changes over unusual comparison chains.","solutions":["Update Ruff to the latest release","Replace `is`/`is not` with `==`/`!=` manually for the literal comparison","Suppress F632 with a `# noqa: F632` comment if the identity check is intentional"],"exampleFix":"# before\nif x is None or y is not 0:\n    pass\n# after\nif x == None or y != 0:\n    pass  # better: `if x is None or y != 0:` with a real literal comparison fixed as ==/!=","handlingStrategy":"validation","validationCode":"# Check comparisons before relying on the fix:\nif x is 'literal':  # F632 flagged; fixer replaces `is` with `==`\n    ...","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use ==/!= for literal comparisons directly","Keep Ruff updated","Review fix suggestions instead of blind --fix on large corpora"],"tags":["ruff","autofix","pyflakes","f632"],"backgroundTag":"autofix-failed-to-apply","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}