{"record":{"id":"930f213b07fa1b51","repo":"rust-lang/rust-analyzer","slug":"equivalent-ancestor-node-should-be-present-in-targ","errorCode":null,"errorMessage":"equivalent ancestor node should be present in target tree","messagePattern":"equivalent ancestor node should be present in target tree","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/syntax/src/syntax_editor/mapping.rs","lineNumber":96,"sourceCode":"\n        // Progressively up-map the input ancestor until we get to the output ancestor\n        let to_output_ancestor = if input_ancestor != output_ancestor {\n            self.upmap_to_ancestor(input_ancestor, output_ancestor)?\n        } else {\n            vec![]\n        };\n\n        let to_map_down =\n            to_output_ancestor.into_iter().rev().chain(to_first_upmap.into_iter().rev());\n\n        let mut target = output_ancestor.clone();\n\n        for index in to_map_down {\n            target = target\n                .children_with_tokens()\n                .nth(index)\n                .and_then(|it| it.into_node())\n                .expect(\"equivalent ancestor node should be present in target tree\");\n        }\n\n        debug_assert_eq!(child.kind(), target.kind());\n\n        Ok(target)\n    }\n\n    fn upmap_to_ancestor(\n        &self,\n        input_ancestor: &SyntaxNode,\n        output_ancestor: &SyntaxNode,\n    ) -> Result<Vec<usize>, MissingMapping> {\n        let mut current =\n            self.upmap_node_single(input_ancestor).unwrap_or_else(|| input_ancestor.clone());\n        let mut upmap_chain = vec![current.index()];\n\n        loop {\n            let Some(parent) = current.parent() else { break };","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/syntax/src/syntax_editor/mapping.rs#L78-L114","documentation":"This panic occurs in rust-analyzer's syntax editor during upmap_child (syntax tree node mapping between original and edited trees). After walking down the target tree by child indices, it asserts that the child at that index exists AND is a node; the .expect fires when the index is out of bounds or the child is a token, meaning the equivalent ancestor structure assumed by the mapping no longer exists in the target tree.","triggerScenarios":"Calling SyntaxMapping::upmap_child (directly or via rewrite_dependent_target/upmap_child_element) with an index into children_with_tokens() that points past the end of the target node's children, or at a token rather than a node — i.e. the source and target trees have diverged structurally at that position.","commonSituations":"Writing a syntax-editor transform whose recorded child indices don't match the tree actually being edited (e.g. applying a mapping built for one syntax revision to another tree, or custom tree mutations that insert/remove children before the mapped index).","solutions":["Ensure the mapping and the target tree come from the same syntax revision; rebuild the mapping after any tree mutation.","Check that indices recorded during mapping (to_map_down) are still valid for the current target node's children_with_tokens().","If a mapped position may be a token, use .and_then(|it| it.into_node()) handling explicitly instead of relying on the expect.","Reproduce with a minimal fixture and file an issue if this fires from a plain edit/assist — it indicates a mapping bug in rust-analyzer itself."],"exampleFix":"// before: index assumed to point at a node\nlet target = target.children_with_tokens().nth(index).and_then(|it| it.into_node())\n    .expect(\"equivalent ancestor node should be present in target tree\");\n// after: defensive fallback in caller-side transform\nlet Some(target) = target.children_with_tokens().nth(index).and_then(|it| it.into_node()) else {\n    return Err(SyntaxEditorError::MappingFailed);\n};","handlingStrategy":"validation","validationCode":"fn mapped_child_is_node(target: &SyntaxNode, index: usize) -> bool {\n    target\n        .children_with_tokens()\n        .nth(index)\n        .map_or(false, |it| it.into_node().is_some())\n}","typeGuard":"fn as_node(el: SyntaxElement) -> Option<SyntaxNode> { el.into_node() }","tryCatchPattern":"// expect! panics rather than returning Result; wrap custom transforms\nlet result = std::panic::catch_unwind(AssertUnwindSafe(|| editor.map_range(range)));\nmatch result { Ok(mapped) => mapped, Err(_) => fallback_range }","preventionTips":["Always derive mappings and query them on the same syntax tree revision.","After mutating a tree, rebuild the SyntaxMapping instead of reusing it.","Prefer APIs returning Option/Result over expect-based internals when writing custom transforms."],"tags":["rust","syntax-tree","internal-invariant","panic"],"backgroundTag":"syntax-tree-mapping-invariant-violation","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}