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.
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)
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.
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
- 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.
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
- the nearest mapped ancestor must map its descendants
- EnumVariants are always nested in Enums
- MatchArms are always nested in MatchExprs
- reachable plan nodes are not discarded
- an edit target must still be present
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/930f213b07fa1b51.
Report an issue: GitHub.