summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs')
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs376
1 files changed, 206 insertions, 170 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
index e6e8d8c02..346cd39a7 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
@@ -1,111 +1,126 @@
//! 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 mbe::{SyntheticToken, SyntheticTokenId, TokenMap};
-use rustc_hash::FxHashMap;
+use base_db::{
+ span::{ErasedFileAstId, SpanAnchor, SpanData},
+ FileId,
+};
+use la_arena::RawIdx;
+use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
+use stdx::never;
use syntax::{
ast::{self, AstNode, HasLoopBody},
- match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
+ match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, TextSize,
+};
+use triomphe::Arc;
+use tt::{Spacing, Span};
+
+use crate::{
+ span::SpanMapRef,
+ tt::{Ident, Leaf, Punct, Subtree},
};
-use tt::token_id::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
/// reverse those changes afterwards, and a token map.
#[derive(Debug, Default)]
pub(crate) struct SyntaxFixups {
- pub(crate) append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
- pub(crate) replace: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
+ pub(crate) append: FxHashMap<SyntaxElement, Vec<Leaf>>,
+ pub(crate) remove: FxHashSet<SyntaxNode>,
pub(crate) undo_info: SyntaxFixupUndoInfo,
- pub(crate) token_map: TokenMap,
- pub(crate) next_id: u32,
}
/// 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 };
}
-const EMPTY_ID: SyntheticTokenId = SyntheticTokenId(!0);
+// censoring -> just don't convert the node
+// replacement -> censor + append
+// append -> insert a fake node, here we need to assemble some dummy span that we can figure out how
+// to remove later
+const FIXUP_DUMMY_FILE: FileId = FileId::from_raw(FileId::MAX_FILE_ID);
+const FIXUP_DUMMY_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(RawIdx::from_u32(!0));
+const FIXUP_DUMMY_RANGE: TextRange = TextRange::empty(TextSize::new(0));
+const FIXUP_DUMMY_RANGE_END: TextSize = TextSize::new(!0);
-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 mut token_map = TokenMap::default();
- let mut next_id = 0;
+ let dummy_range = FIXUP_DUMMY_RANGE;
+ // 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: FIXUP_DUMMY_FILE, ast_id: FIXUP_DUMMY_AST_ID };
+ 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 node = match event {
- syntax::WalkEvent::Enter(node) => node,
- syntax::WalkEvent::Leave(_) => continue,
- };
+ 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), FIXUP_DUMMY_RANGE_END),
+ 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) => {
if it.name_ref().is_none() {
// incomplete field access: some_expr.|
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::IDENT,
+ Leaf::Ident(Ident {
text: "__ra_fixup".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ span: fake_span(node_range),
+ }),
]);
}
},
ast::ExprStmt(it) => {
if it.semicolon_token().is_none() {
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::SEMICOLON,
- text: ";".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ Leaf::Punct(Punct {
+ char: ';',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range),
+ }),
]);
}
},
ast::LetStmt(it) => {
if it.semicolon_token().is_none() {
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::SEMICOLON,
- text: ";".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ Leaf::Punct(Punct {
+ char: ';',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
]);
}
},
@@ -117,28 +132,25 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
None => continue,
};
append.insert(if_token.into(), vec![
- SyntheticToken {
- kind: SyntaxKind::IDENT,
+ Leaf::Ident(Ident {
text: "__ra_fixup".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ span: fake_span(node_range)
+ }),
]);
}
if it.then_branch().is_none() {
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::L_CURLY,
- text: "{".into(),
- range: end_range,
- id: EMPTY_ID,
- },
- SyntheticToken {
- kind: SyntaxKind::R_CURLY,
- text: "}".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ // FIXME: THis should be a subtree no?
+ Leaf::Punct(Punct {
+ char: '{',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
+ Leaf::Punct(Punct {
+ char: '}',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
]);
}
},
@@ -150,46 +162,42 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
None => continue,
};
append.insert(while_token.into(), vec![
- SyntheticToken {
- kind: SyntaxKind::IDENT,
+ Leaf::Ident(Ident {
text: "__ra_fixup".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ span: fake_span(node_range)
+ }),
]);
}
if it.loop_body().is_none() {
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::L_CURLY,
- text: "{".into(),
- range: end_range,
- id: EMPTY_ID,
- },
- SyntheticToken {
- kind: SyntaxKind::R_CURLY,
- text: "}".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ // FIXME: THis should be a subtree no?
+ Leaf::Punct(Punct {
+ char: '{',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
+ Leaf::Punct(Punct {
+ char: '}',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
]);
}
},
ast::LoopExpr(it) => {
if it.loop_body().is_none() {
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::L_CURLY,
- text: "{".into(),
- range: end_range,
- id: EMPTY_ID,
- },
- SyntheticToken {
- kind: SyntaxKind::R_CURLY,
- text: "}".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ // FIXME: THis should be a subtree no?
+ Leaf::Punct(Punct {
+ char: '{',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
+ Leaf::Punct(Punct {
+ char: '}',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
]);
}
},
@@ -201,29 +209,26 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
None => continue
};
append.insert(match_token.into(), vec![
- SyntheticToken {
- kind: SyntaxKind::IDENT,
+ Leaf::Ident(Ident {
text: "__ra_fixup".into(),
- range: end_range,
- id: EMPTY_ID
- },
+ span: fake_span(node_range)
+ }),
]);
}
if it.match_arm_list().is_none() {
// No match arms
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::L_CURLY,
- text: "{".into(),
- range: end_range,
- id: EMPTY_ID,
- },
- SyntheticToken {
- kind: SyntaxKind::R_CURLY,
- text: "}".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ // FIXME: THis should be a subtree no?
+ Leaf::Punct(Punct {
+ char: '{',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
+ Leaf::Punct(Punct {
+ char: '}',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
]);
}
},
@@ -234,10 +239,15 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
};
let [pat, in_token, iter] = [
- (SyntaxKind::UNDERSCORE, "_"),
- (SyntaxKind::IN_KW, "in"),
- (SyntaxKind::IDENT, "__ra_fixup")
- ].map(|(kind, text)| SyntheticToken { kind, text: text.into(), range: end_range, id: EMPTY_ID});
+ "_",
+ "in",
+ "__ra_fixup"
+ ].map(|text|
+ Leaf::Ident(Ident {
+ text: text.into(),
+ span: fake_span(node_range)
+ }),
+ );
if it.pat().is_none() && it.in_token().is_none() && it.iterable().is_none() {
append.insert(for_token.into(), vec![pat, in_token, iter]);
@@ -248,18 +258,17 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
if it.loop_body().is_none() {
append.insert(node.clone().into(), vec![
- SyntheticToken {
- kind: SyntaxKind::L_CURLY,
- text: "{".into(),
- range: end_range,
- id: EMPTY_ID,
- },
- SyntheticToken {
- kind: SyntaxKind::R_CURLY,
- text: "}".into(),
- range: end_range,
- id: EMPTY_ID,
- },
+ // FIXME: THis should be a subtree no?
+ Leaf::Punct(Punct {
+ char: '{',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
+ Leaf::Punct(Punct {
+ char: '}',
+ spacing: Spacing::Alone,
+ span: fake_span(node_range)
+ }),
]);
}
},
@@ -267,12 +276,13 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
}
}
}
+ let needs_fixups = !append.is_empty() || !original.is_empty();
SyntaxFixups {
append,
- replace,
- token_map,
- next_id,
- undo_info: SyntaxFixupUndoInfo { original: original.into_boxed_slice() },
+ remove,
+ undo_info: SyntaxFixupUndoInfo {
+ original: needs_fixups.then(|| Arc::new(original.into_boxed_slice())),
+ },
}
}
@@ -288,36 +298,57 @@ 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,
- token_map: &TokenMap,
- undo_info: &SyntaxFixupUndoInfo,
-) {
+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;
+ if never!(
+ tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE
+ || tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE
+ ) {
+ tt.delimiter.close = SpanData::DUMMY;
+ tt.delimiter.open = SpanData::DUMMY;
+ }
+ 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) => {
- token_map.synthetic_token_id(*leaf.span()) != Some(EMPTY_ID)
- }
- tt::TokenTree::Subtree(st) => {
- token_map.synthetic_token_id(st.delimiter.open) != Some(EMPTY_ID)
+ let span = leaf.span();
+ let is_real_leaf = span.anchor.file_id != FIXUP_DUMMY_FILE;
+ let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
+ is_real_leaf || is_replaced_node
}
+ tt::TokenTree::Subtree(_) => true,
})
.flat_map(|tt| match tt {
tt::TokenTree::Subtree(mut tt) => {
- reverse_fixups(&mut tt, token_map, undo_info);
+ if tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE
+ || tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE
+ {
+ // 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();
+ }
+ reverse_fixups_(&mut tt, undo_info);
SmallVec::from_const([tt.into()])
}
tt::TokenTree::Leaf(leaf) => {
- if let Some(id) = token_map.synthetic_token_id(*leaf.span()) {
- let original = undo_info.original[id.0 as usize].clone();
+ if leaf.span().anchor.file_id == FIXUP_DUMMY_FILE {
+ // 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 {
original.token_trees.into()
} else {
SmallVec::from_const([original.into()])
}
} else {
+ // just a normal leaf
SmallVec::from_const([leaf.into()])
}
}
@@ -327,11 +358,15 @@ pub(crate) fn reverse_fixups(
#[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()`.
@@ -361,13 +396,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::from_raw(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");
@@ -383,14 +418,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:?}"
);
}
@@ -403,7 +439,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {for _ in __ra_fixup {}}
+fn foo () {for _ in __ra_fixup { }}
"#]],
)
}
@@ -431,7 +467,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {for bar in qux {}}
+fn foo () {for bar in qux { }}
"#]],
)
}
@@ -462,7 +498,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {match __ra_fixup {}}
+fn foo () {match __ra_fixup { }}
"#]],
)
}
@@ -494,7 +530,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {match __ra_fixup {}}
+fn foo () {match __ra_fixup { }}
"#]],
)
}
@@ -609,7 +645,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {if a {}}
+fn foo () {if a { }}
"#]],
)
}
@@ -623,7 +659,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {if __ra_fixup {}}
+fn foo () {if __ra_fixup { }}
"#]],
)
}
@@ -637,7 +673,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {if __ra_fixup {} {}}
+fn foo () {if __ra_fixup {} { }}
"#]],
)
}
@@ -651,7 +687,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {while __ra_fixup {}}
+fn foo () {while __ra_fixup { }}
"#]],
)
}
@@ -665,7 +701,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {while foo {}}
+fn foo () {while foo { }}
"#]],
)
}
@@ -692,7 +728,7 @@ fn foo() {
}
"#,
expect![[r#"
-fn foo () {loop {}}
+fn foo () {loop { }}
"#]],
)
}