Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/fixup.rs')
-rw-r--r--crates/hir-expand/src/fixup.rs163
1 files changed, 111 insertions, 52 deletions
diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs
index 0af29681a1..3d2d52a0af 100644
--- a/crates/hir-expand/src/fixup.rs
+++ b/crates/hir-expand/src/fixup.rs
@@ -3,7 +3,6 @@
use intern::sym;
use rustc_hash::{FxHashMap, FxHashSet};
-use smallvec::SmallVec;
use span::{
ErasedFileAstId, Span, SpanAnchor, SyntaxContextId, FIXUP_ERASED_FILE_AST_ID_MARKER,
ROOT_ERASED_FILE_AST_ID,
@@ -19,7 +18,7 @@ use tt::Spacing;
use crate::{
span_map::SpanMapRef,
- tt::{Ident, Leaf, Punct, Subtree},
+ tt::{self, Ident, Leaf, Punct, TopSubtree},
};
/// The result of calculating fixes for a syntax node -- a bunch of changes
@@ -36,7 +35,7 @@ pub(crate) struct SyntaxFixups {
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SyntaxFixupUndoInfo {
// FIXME: ThinArc<[Subtree]>
- original: Option<Arc<Box<[Subtree]>>>,
+ original: Option<Arc<Box<[TopSubtree]>>>,
}
impl SyntaxFixupUndoInfo {
@@ -110,7 +109,7 @@ pub(crate) fn fixup_syntax(
}
},
ast::ExprStmt(it) => {
- let needs_semi = it.semicolon_token().is_none() && it.expr().map_or(false, |e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR);
+ let needs_semi = it.semicolon_token().is_none() && it.expr().is_some_and(|e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR);
if needs_semi {
append.insert(node.clone().into(), vec![
Leaf::Punct(Punct {
@@ -369,68 +368,126 @@ fn has_error_to_handle(node: &SyntaxNode) -> bool {
has_error(node) || node.children().any(|c| !can_handle_error(&c) && has_error_to_handle(&c))
}
-pub(crate) fn reverse_fixups(tt: &mut Subtree, undo_info: &SyntaxFixupUndoInfo) {
+pub(crate) fn reverse_fixups(tt: &mut TopSubtree, undo_info: &SyntaxFixupUndoInfo) {
let Some(undo_info) = undo_info.original.as_deref() else { return };
let undo_info = &**undo_info;
+ let delimiter = tt.top_subtree_delimiter_mut();
#[allow(deprecated)]
if never!(
- tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
- || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
+ delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
+ || delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
) {
let span = |file_id| Span {
range: TextRange::empty(TextSize::new(0)),
anchor: SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID },
ctx: SyntaxContextId::ROOT,
};
- tt.delimiter.open = span(tt.delimiter.open.anchor.file_id);
- tt.delimiter.close = span(tt.delimiter.close.anchor.file_id);
+ delimiter.open = span(delimiter.open.anchor.file_id);
+ delimiter.close = span(delimiter.close.anchor.file_id);
}
reverse_fixups_(tt, undo_info);
}
-fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) {
- let tts = std::mem::take(&mut tt.token_trees).into_vec();
- tt.token_trees = tts
- .into_iter()
- // delete all fake nodes
- .filter(|tt| match tt {
- tt::TokenTree::Leaf(leaf) => {
- let span = leaf.span();
- let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID;
- let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
- is_real_leaf || is_replaced_node
+#[derive(Debug)]
+enum TransformTtAction<'a> {
+ Keep,
+ ReplaceWith(tt::TokenTreesView<'a>),
+}
+
+impl TransformTtAction<'_> {
+ fn remove() -> Self {
+ Self::ReplaceWith(tt::TokenTreesView::new(&[]))
+ }
+}
+
+/// This function takes a token tree, and calls `callback` with each token tree in it.
+/// Then it does what the callback says: keeps the tt or replaces it with a (possibly empty)
+/// tts view.
+fn transform_tt<'a, 'b>(
+ tt: &'a mut Vec<tt::TokenTree>,
+ mut callback: impl FnMut(&mut tt::TokenTree) -> TransformTtAction<'b>,
+) {
+ // We need to keep a stack of the currently open subtrees, because we need to update
+ // them if we change the number of items in them.
+ let mut subtrees_stack = Vec::new();
+ let mut i = 0;
+ while i < tt.len() {
+ 'pop_finished_subtrees: while let Some(&subtree_idx) = subtrees_stack.last() {
+ let tt::TokenTree::Subtree(subtree) = &tt[subtree_idx] else {
+ unreachable!("non-subtree on subtrees stack");
+ };
+ if i >= subtree_idx + 1 + subtree.usize_len() {
+ subtrees_stack.pop();
+ } else {
+ break 'pop_finished_subtrees;
}
- tt::TokenTree::Subtree(_) => true,
- })
- .flat_map(|tt| match tt {
- tt::TokenTree::Subtree(mut tt) => {
- if tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
- || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
- {
- // Even though fixup never creates subtrees with fixup spans, the old proc-macro server
- // might copy them if the proc-macro asks for it, so we need to filter those out
- // here as well.
- return SmallVec::new_const();
+ }
+
+ let action = callback(&mut tt[i]);
+ match action {
+ TransformTtAction::Keep => {
+ // This cannot be shared with the replaced case, because then we may push the same subtree
+ // twice, and will update it twice which will lead to errors.
+ if let tt::TokenTree::Subtree(_) = &tt[i] {
+ subtrees_stack.push(i);
}
- reverse_fixups_(&mut tt, undo_info);
- SmallVec::from_const([tt.into()])
+
+ i += 1;
}
- tt::TokenTree::Leaf(leaf) => {
- if leaf.span().anchor.ast_id == FIXUP_DUMMY_AST_ID {
- // we have a fake node here, we need to replace it again with the original
- let original = undo_info[u32::from(leaf.span().range.start()) as usize].clone();
- if original.delimiter.kind == tt::DelimiterKind::Invisible {
- SmallVec::from(original.token_trees.into_vec())
- } else {
- SmallVec::from_const([original.into()])
- }
- } else {
- // just a normal leaf
- SmallVec::from_const([leaf.into()])
+ TransformTtAction::ReplaceWith(replacement) => {
+ let old_len = 1 + match &tt[i] {
+ tt::TokenTree::Leaf(_) => 0,
+ tt::TokenTree::Subtree(subtree) => subtree.usize_len(),
+ };
+ let len_diff = replacement.len() as i64 - old_len as i64;
+ tt.splice(i..i + old_len, replacement.flat_tokens().iter().cloned());
+ // `+1` for the loop.
+ i = i.checked_add_signed(len_diff as isize + 1).unwrap();
+
+ for &subtree_idx in &subtrees_stack {
+ let tt::TokenTree::Subtree(subtree) = &mut tt[subtree_idx] else {
+ unreachable!("non-subtree on subtrees stack");
+ };
+ subtree.len = (i64::from(subtree.len) + len_diff).try_into().unwrap();
}
}
- })
- .collect();
+ }
+ }
+}
+
+fn reverse_fixups_(tt: &mut TopSubtree, undo_info: &[TopSubtree]) {
+ let mut tts = std::mem::take(&mut tt.0).into_vec();
+ transform_tt(&mut tts, |tt| match tt {
+ tt::TokenTree::Leaf(leaf) => {
+ let span = leaf.span();
+ let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID;
+ let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
+ if !is_real_leaf && !is_replaced_node {
+ return TransformTtAction::remove();
+ }
+
+ if !is_real_leaf {
+ // we have a fake node here, we need to replace it again with the original
+ let original = &undo_info[u32::from(leaf.span().range.start()) as usize];
+ TransformTtAction::ReplaceWith(original.view().strip_invisible())
+ } else {
+ // just a normal leaf
+ TransformTtAction::Keep
+ }
+ }
+ tt::TokenTree::Subtree(tt) => {
+ if tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
+ || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
+ {
+ // Even though fixup never creates subtrees with fixup spans, the old proc-macro server
+ // might copy them if the proc-macro asks for it, so we need to filter those out
+ // here as well.
+ return TransformTtAction::remove();
+ }
+ TransformTtAction::Keep
+ }
+ });
+ tt.0 = tts.into_boxed_slice();
}
#[cfg(test)]
@@ -458,16 +515,18 @@ mod tests {
}
}
- fn check_subtree_eq(a: &tt::Subtree, b: &tt::Subtree) -> bool {
- a.delimiter.kind == b.delimiter.kind
- && a.token_trees.len() == b.token_trees.len()
- && a.token_trees.iter().zip(b.token_trees.iter()).all(|(a, b)| check_tt_eq(a, b))
+ fn check_subtree_eq(a: &tt::TopSubtree, b: &tt::TopSubtree) -> bool {
+ let a = a.view().as_token_trees().flat_tokens();
+ let b = b.view().as_token_trees().flat_tokens();
+ a.len() == b.len() && std::iter::zip(a, b).all(|(a, b)| check_tt_eq(a, b))
}
fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool {
match (a, b) {
(tt::TokenTree::Leaf(a), tt::TokenTree::Leaf(b)) => check_leaf_eq(a, b),
- (tt::TokenTree::Subtree(a), tt::TokenTree::Subtree(b)) => check_subtree_eq(a, b),
+ (tt::TokenTree::Subtree(a), tt::TokenTree::Subtree(b)) => {
+ a.delimiter.kind == b.delimiter.kind
+ }
_ => false,
}
}