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.rs | 206 |
1 files changed, 114 insertions, 92 deletions
diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs index 8fa7d57629..77ddf7a48c 100644 --- a/crates/hir-expand/src/fixup.rs +++ b/crates/hir-expand/src/fixup.rs @@ -1,22 +1,24 @@ //! To make attribute macros work reliably when typing, we need to take care to //! fix up syntax errors in the code we're passing to them. -use std::mem; use base_db::{ - span::{ErasedFileAstId, SpanAnchor, SpanData, SyntaxContextId}, + span::{ErasedFileAstId, SpanAnchor, SpanData}, FileId, }; use la_arena::RawIdx; -use mbe::TokenMap; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use syntax::{ ast::{self, AstNode, HasLoopBody}, match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, TextSize, }; +use triomphe::Arc; use tt::Spacing; -use crate::tt::{Ident, Leaf, Punct, Subtree}; +use crate::{ + span::SpanMapRef, + tt::{Ident, Leaf, Punct, Subtree}, +}; /// The result of calculating fixes for a syntax node -- a bunch of changes /// (appending to and replacing nodes), the information that is needed to @@ -24,14 +26,19 @@ use crate::tt::{Ident, Leaf, Punct, Subtree}; #[derive(Debug, Default)] pub(crate) struct SyntaxFixups { pub(crate) append: FxHashMap<SyntaxElement, Vec<Leaf>>, - pub(crate) replace: FxHashMap<SyntaxElement, Vec<()>>, + pub(crate) remove: FxHashSet<SyntaxNode>, pub(crate) undo_info: SyntaxFixupUndoInfo, } /// This is the information needed to reverse the fixups. -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct SyntaxFixupUndoInfo { - original: Box<[Subtree]>, + // FIXME: ThinArc<[Subtree]> + original: Option<Arc<Box<[Subtree]>>>, +} + +impl SyntaxFixupUndoInfo { + pub(crate) const NONE: Self = SyntaxFixupUndoInfo { original: None }; } // censoring -> just don't convert the node @@ -39,47 +46,45 @@ pub struct SyntaxFixupUndoInfo { // append -> insert a fake node, here we need to assemble some dummy span that we can figure out how // to remove later -pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { +pub(crate) fn fixup_syntax(span_map: SpanMapRef<'_>, node: &SyntaxNode) -> SyntaxFixups { let mut append = FxHashMap::<SyntaxElement, _>::default(); - let mut replace = FxHashMap::<SyntaxElement, _>::default(); + let mut remove = FxHashSet::<SyntaxNode>::default(); let mut preorder = node.preorder(); let mut original = Vec::new(); let dummy_range = TextRange::empty(TextSize::new(0)); + // we use a file id of `FileId(!0)` to signal a fake node, and the text range's start offset as + // the index into the replacement vec but only if the end points to !0 let dummy_anchor = - SpanAnchor { file_id: FileId(!0), ast_id: ErasedFileAstId::from_raw(RawIdx::from(0)) }; - let fake_span = - SpanData { range: dummy_range, anchor: dummy_anchor, ctx: SyntaxContextId::FAKE }; + SpanAnchor { file_id: FileId(!0), ast_id: ErasedFileAstId::from_raw(RawIdx::from(!0)) }; + let fake_span = |range| SpanData { + range: dummy_range, + anchor: dummy_anchor, + ctx: span_map.span_for_range(range).ctx, + }; while let Some(event) = preorder.next() { let syntax::WalkEvent::Enter(node) = event else { continue }; - /* + let node_range = node.text_range(); if can_handle_error(&node) && has_error_to_handle(&node) { + remove.insert(node.clone().into()); // the node contains an error node, we have to completely replace it by something valid - let (original_tree, new_tmap, new_next_id) = - mbe::syntax_node_to_token_tree_with_modifications( - &node, - mem::take(&mut token_map), - next_id, - Default::default(), - Default::default(), - ); - token_map = new_tmap; - next_id = new_next_id; + let original_tree = mbe::syntax_node_to_token_tree(&node, span_map); let idx = original.len() as u32; original.push(original_tree); - let replacement = SyntheticToken { - kind: SyntaxKind::IDENT, + let replacement = Leaf::Ident(Ident { text: "__ra_fixup".into(), - range: node.text_range(), - id: SyntheticTokenId(idx), - }; - replace.insert(node.clone().into(), vec![replacement]); + span: SpanData { + range: TextRange::new(TextSize::new(idx), TextSize::new(!0)), + anchor: dummy_anchor, + ctx: span_map.span_for_range(node_range).ctx, + }, + }); + append.insert(node.clone().into(), vec![replacement]); preorder.skip_subtree(); continue; } - */ + // In some other situations, we can fix things by just appending some tokens. - let end_range = TextRange::empty(node.text_range().end()); match_ast! { match node { ast::FieldExpr(it) => { @@ -88,7 +93,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { append.insert(node.clone().into(), vec![ Leaf::Ident(Ident { text: "__ra_fixup".into(), - span: fake_span + span: fake_span(node_range), }), ]); } @@ -99,7 +104,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: ';', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range), }), ]); } @@ -110,7 +115,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: ';', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), ]); } @@ -125,7 +130,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { append.insert(if_token.into(), vec![ Leaf::Ident(Ident { text: "__ra_fixup".into(), - span: fake_span + span: fake_span(node_range) }), ]); } @@ -135,12 +140,12 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: '{', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), Leaf::Punct(Punct { char: '}', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), ]); } @@ -155,7 +160,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { append.insert(while_token.into(), vec![ Leaf::Ident(Ident { text: "__ra_fixup".into(), - span: fake_span + span: fake_span(node_range) }), ]); } @@ -165,12 +170,12 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: '{', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), Leaf::Punct(Punct { char: '}', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), ]); } @@ -182,12 +187,12 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: '{', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), Leaf::Punct(Punct { char: '}', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), ]); } @@ -202,7 +207,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { append.insert(match_token.into(), vec![ Leaf::Ident(Ident { text: "__ra_fixup".into(), - span: fake_span + span: fake_span(node_range) }), ]); } @@ -213,12 +218,12 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: '{', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), Leaf::Punct(Punct { char: '}', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), ]); } @@ -236,7 +241,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { ].map(|text| Leaf::Ident(Ident { text: text.into(), - span: fake_span + span: fake_span(node_range) }), ); @@ -253,12 +258,12 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { Leaf::Punct(Punct { char: '{', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), Leaf::Punct(Punct { char: '}', spacing: Spacing::Alone, - span: fake_span + span: fake_span(node_range) }), ]); } @@ -267,10 +272,13 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups { } } } + let needs_fixups = !append.is_empty() || !original.is_empty(); SyntaxFixups { append, - replace, - undo_info: SyntaxFixupUndoInfo { original: original.into_boxed_slice() }, + remove, + undo_info: SyntaxFixupUndoInfo { + original: needs_fixups.then(|| Arc::new(original.into_boxed_slice())), + }, } } @@ -287,42 +295,55 @@ fn has_error_to_handle(node: &SyntaxNode) -> bool { } pub(crate) fn reverse_fixups(tt: &mut Subtree, undo_info: &SyntaxFixupUndoInfo) { + let Some(undo_info) = undo_info.original.as_deref() else { return }; + let undo_info = &**undo_info; + reverse_fixups_(tt, undo_info); +} + +fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) { let tts = std::mem::take(&mut tt.token_trees); tt.token_trees = tts .into_iter() // delete all fake nodes .filter(|tt| match tt { - tt::TokenTree::Leaf(leaf) => leaf.span().ctx != SyntaxContextId::FAKE, - tt::TokenTree::Subtree(st) => st.delimiter.open.ctx != SyntaxContextId::FAKE, + tt::TokenTree::Leaf(leaf) => { + let span = leaf.span(); + span.anchor.file_id != FileId(!0) || span.range.end() == TextSize::new(!0) + } + tt::TokenTree::Subtree(_) => true, + }) + .flat_map(|tt| match tt { + tt::TokenTree::Subtree(mut tt) => { + reverse_fixups_(&mut tt, undo_info); + SmallVec::from_const([tt.into()]) + } + tt::TokenTree::Leaf(leaf) => { + if leaf.span().anchor.file_id == FileId(!0) { + let original = undo_info[u32::from(leaf.span().range.start()) as usize].clone(); + if original.delimiter.kind == tt::DelimiterKind::Invisible { + original.token_trees.into() + } else { + SmallVec::from_const([original.into()]) + } + } else { + SmallVec::from_const([leaf.into()]) + } + } }) - // .flat_map(|tt| match tt { - // tt::TokenTree::Subtree(mut tt) => { - // reverse_fixups(&mut tt, undo_info); - // SmallVec::from_const([tt.into()]) - // } - // tt::TokenTree::Leaf(leaf) => { - // if let Some(id) = leaf.span().anchor { - // let original = undo_info.original[id.0 as usize].clone(); - // if original.delimiter.kind == tt::DelimiterKind::Invisible { - // original.token_trees.into() - // } else { - // SmallVec::from_const([original.into()]) - // } - // } else { - // SmallVec::from_const([leaf.into()]) - // } - // } - // }) .collect(); } #[cfg(test)] mod tests { + use base_db::FileId; use expect_test::{expect, Expect}; + use triomphe::Arc; - use crate::tt; - - use super::reverse_fixups; + use crate::{ + fixup::reverse_fixups, + span::{RealSpanMap, SpanMap}, + tt, + }; // The following three functions are only meant to check partial structural equivalence of // `TokenTree`s, see the last assertion in `check()`. @@ -352,13 +373,13 @@ mod tests { #[track_caller] fn check(ra_fixture: &str, mut expect: Expect) { let parsed = syntax::SourceFile::parse(ra_fixture); - let fixups = super::fixup_syntax(&parsed.syntax_node()); - let (mut tt, tmap, _) = mbe::syntax_node_to_token_tree_with_modifications( + let span_map = SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(FileId(0)))); + let fixups = super::fixup_syntax(span_map.as_ref(), &parsed.syntax_node()); + let mut tt = mbe::syntax_node_to_token_tree_modified( &parsed.syntax_node(), - fixups.token_map, - fixups.next_id, - fixups.replace, + span_map.as_ref(), fixups.append, + fixups.remove, ); let actual = format!("{tt}\n"); @@ -374,14 +395,15 @@ mod tests { parse.syntax_node() ); - reverse_fixups(&mut tt, &tmap, &fixups.undo_info); + reverse_fixups(&mut tt, &fixups.undo_info); // the fixed-up + reversed version should be equivalent to the original input // modulo token IDs and `Punct`s' spacing. - let (original_as_tt, _) = mbe::syntax_node_to_token_tree(&parsed.syntax_node()); + let original_as_tt = + mbe::syntax_node_to_token_tree(&parsed.syntax_node(), span_map.as_ref()); assert!( check_subtree_eq(&tt, &original_as_tt), - "different token tree: {tt:?},\n{original_as_tt:?}" + "different token tree:\n{tt:?}\n\n{original_as_tt:?}" ); } @@ -394,7 +416,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {for _ in __ra_fixup {}} +fn foo () {for _ in __ra_fixup { }} "#]], ) } @@ -422,7 +444,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {for bar in qux {}} +fn foo () {for bar in qux { }} "#]], ) } @@ -453,7 +475,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {match __ra_fixup {}} +fn foo () {match __ra_fixup { }} "#]], ) } @@ -485,7 +507,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {match __ra_fixup {}} +fn foo () {match __ra_fixup { }} "#]], ) } @@ -600,7 +622,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {if a {}} +fn foo () {if a { }} "#]], ) } @@ -614,7 +636,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {if __ra_fixup {}} +fn foo () {if __ra_fixup { }} "#]], ) } @@ -628,7 +650,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {if __ra_fixup {} {}} +fn foo () {if __ra_fixup {} { }} "#]], ) } @@ -642,7 +664,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {while __ra_fixup {}} +fn foo () {while __ra_fixup { }} "#]], ) } @@ -656,7 +678,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {while foo {}} +fn foo () {while foo { }} "#]], ) } @@ -683,7 +705,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {loop {}} +fn foo () {loop { }} "#]], ) } |