rust-lang/rust-analyzer · error

equivalent ancestor node should be present in target tree

Error message

equivalent ancestor node should be present in target tree

What it means

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.

Solutions

  1. Ensure the mapping and the target tree come from the same syntax revision; rebuild the mapping after any tree mutation.
  2. Check that indices recorded during mapping (to_map_down) are still valid for the current target node's children_with_tokens().
  3. If a mapped position may be a token, use .and_then(|it| it.into_node()) handling explicitly instead of relying on the expect.
  4. 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.

Example fix

// before: index assumed to point at a node
let target = target.children_with_tokens().nth(index).and_then(|it| it.into_node())
    .expect("equivalent ancestor node should be present in target tree");
// after: defensive fallback in caller-side transform
let Some(target) = target.children_with_tokens().nth(index).and_then(|it| it.into_node()) else {
    return Err(SyntaxEditorError::MappingFailed);
};
Defensive patterns

Strategy: validation

Validate before calling

fn mapped_child_is_node(target: &SyntaxNode, index: usize) -> bool {
    target
        .children_with_tokens()
        .nth(index)
        .map_or(false, |it| it.into_node().is_some())
}

Type guard

fn as_node(el: SyntaxElement) -> Option<SyntaxNode> { el.into_node() }

Try / catch

// expect! panics rather than returning Result; wrap custom transforms
let result = std::panic::catch_unwind(AssertUnwindSafe(|| editor.map_range(range)));
match result { Ok(mapped) => mapped, Err(_) => fallback_range }

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/930f213b07fa1b51. Report an issue: GitHub.

Appendix: source

Thrown at crates/syntax/src/syntax_editor/mapping.rs:96

        // Progressively up-map the input ancestor until we get to the output ancestor
        let to_output_ancestor = if input_ancestor != output_ancestor {
            self.upmap_to_ancestor(input_ancestor, output_ancestor)?
        } else {
            vec![]
        };

        let to_map_down =
            to_output_ancestor.into_iter().rev().chain(to_first_upmap.into_iter().rev());

        let mut target = output_ancestor.clone();

        for index in to_map_down {
            target = target
                .children_with_tokens()
                .nth(index)
                .and_then(|it| it.into_node())
                .expect("equivalent ancestor node should be present in target tree");
        }

        debug_assert_eq!(child.kind(), target.kind());

        Ok(target)
    }

    fn upmap_to_ancestor(
        &self,
        input_ancestor: &SyntaxNode,
        output_ancestor: &SyntaxNode,
    ) -> Result<Vec<usize>, MissingMapping> {
        let mut current =
            self.upmap_node_single(input_ancestor).unwrap_or_else(|| input_ancestor.clone());
        let mut upmap_chain = vec![current.index()];

        loop {
            let Some(parent) = current.parent() else { break };

View on GitHub (pinned to e8f7e90aa3)