]> git.lizzy.rs Git - rust.git/blob - crates/hir_expand/src/fixup.rs
Merge #11461
[rust.git] / crates / hir_expand / src / fixup.rs
1 //! To make attribute macros work reliably when typing, we need to take care to
2 //! fix up syntax errors in the code we're passing to them.
3 use std::mem;
4
5 use mbe::{SyntheticToken, SyntheticTokenId, TokenMap};
6 use rustc_hash::FxHashMap;
7 use syntax::{
8     ast::{self, AstNode},
9     match_ast, SyntaxKind, SyntaxNode, TextRange,
10 };
11 use tt::Subtree;
12
13 /// The result of calculating fixes for a syntax node -- a bunch of changes
14 /// (appending to and replacing nodes), the information that is needed to
15 /// reverse those changes afterwards, and a token map.
16 #[derive(Debug)]
17 pub(crate) struct SyntaxFixups {
18     pub(crate) append: FxHashMap<SyntaxNode, Vec<SyntheticToken>>,
19     pub(crate) replace: FxHashMap<SyntaxNode, Vec<SyntheticToken>>,
20     pub(crate) undo_info: SyntaxFixupUndoInfo,
21     pub(crate) token_map: TokenMap,
22     pub(crate) next_id: u32,
23 }
24
25 /// This is the information needed to reverse the fixups.
26 #[derive(Debug, PartialEq, Eq)]
27 pub struct SyntaxFixupUndoInfo {
28     original: Vec<Subtree>,
29 }
30
31 const EMPTY_ID: SyntheticTokenId = SyntheticTokenId(!0);
32
33 pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
34     let mut append = FxHashMap::default();
35     let mut replace = FxHashMap::default();
36     let mut preorder = node.preorder();
37     let mut original = Vec::new();
38     let mut token_map = TokenMap::default();
39     let mut next_id = 0;
40     while let Some(event) = preorder.next() {
41         let node = match event {
42             syntax::WalkEvent::Enter(node) => node,
43             syntax::WalkEvent::Leave(_) => continue,
44         };
45
46         if can_handle_error(&node) && has_error_to_handle(&node) {
47             // the node contains an error node, we have to completely replace it by something valid
48             let (original_tree, new_tmap, new_next_id) =
49                 mbe::syntax_node_to_token_tree_with_modifications(
50                     &node,
51                     mem::take(&mut token_map),
52                     next_id,
53                     Default::default(),
54                     Default::default(),
55                 );
56             token_map = new_tmap;
57             next_id = new_next_id;
58             let idx = original.len() as u32;
59             original.push(original_tree);
60             let replacement = SyntheticToken {
61                 kind: SyntaxKind::IDENT,
62                 text: "__ra_fixup".into(),
63                 range: node.text_range(),
64                 id: SyntheticTokenId(idx),
65             };
66             replace.insert(node.clone(), vec![replacement]);
67             preorder.skip_subtree();
68             continue;
69         }
70
71         // In some other situations, we can fix things by just appending some tokens.
72         let end_range = TextRange::empty(node.text_range().end());
73         match_ast! {
74             match node {
75                 ast::FieldExpr(it) => {
76                     if it.name_ref().is_none() {
77                         // incomplete field access: some_expr.|
78                         append.insert(node.clone(), vec![
79                             SyntheticToken {
80                                 kind: SyntaxKind::IDENT,
81                                 text: "__ra_fixup".into(),
82                                 range: end_range,
83                                 id: EMPTY_ID,
84                             },
85                         ]);
86                     }
87                 },
88                 ast::ExprStmt(it) => {
89                     if it.semicolon_token().is_none() {
90                         append.insert(node.clone(), vec![
91                             SyntheticToken {
92                                 kind: SyntaxKind::SEMICOLON,
93                                 text: ";".into(),
94                                 range: end_range,
95                                 id: EMPTY_ID,
96                             },
97                         ]);
98                     }
99                 },
100                 _ => (),
101             }
102         }
103     }
104     SyntaxFixups {
105         append,
106         replace,
107         token_map,
108         next_id,
109         undo_info: SyntaxFixupUndoInfo { original },
110     }
111 }
112
113 fn has_error(node: &SyntaxNode) -> bool {
114     node.children().any(|c| c.kind() == SyntaxKind::ERROR)
115 }
116
117 fn can_handle_error(node: &SyntaxNode) -> bool {
118     ast::Expr::can_cast(node.kind())
119 }
120
121 fn has_error_to_handle(node: &SyntaxNode) -> bool {
122     has_error(node) || node.children().any(|c| !can_handle_error(&c) && has_error_to_handle(&c))
123 }
124
125 pub(crate) fn reverse_fixups(
126     tt: &mut Subtree,
127     token_map: &TokenMap,
128     undo_info: &SyntaxFixupUndoInfo,
129 ) {
130     tt.token_trees.retain(|tt| match tt {
131         tt::TokenTree::Leaf(leaf) => {
132             token_map.synthetic_token_id(leaf.id()).is_none()
133                 || token_map.synthetic_token_id(leaf.id()) != Some(EMPTY_ID)
134         }
135         _ => true,
136     });
137     tt.token_trees.iter_mut().for_each(|tt| match tt {
138         tt::TokenTree::Subtree(tt) => reverse_fixups(tt, token_map, undo_info),
139         tt::TokenTree::Leaf(leaf) => {
140             if let Some(id) = token_map.synthetic_token_id(leaf.id()) {
141                 let original = &undo_info.original[id.0 as usize];
142                 *tt = tt::TokenTree::Subtree(original.clone());
143             }
144         }
145     });
146 }
147
148 #[cfg(test)]
149 mod tests {
150     use expect_test::{expect, Expect};
151
152     use super::reverse_fixups;
153
154     #[track_caller]
155     fn check(ra_fixture: &str, mut expect: Expect) {
156         let parsed = syntax::SourceFile::parse(ra_fixture);
157         let fixups = super::fixup_syntax(&parsed.syntax_node());
158         let (mut tt, tmap, _) = mbe::syntax_node_to_token_tree_with_modifications(
159             &parsed.syntax_node(),
160             fixups.token_map,
161             fixups.next_id,
162             fixups.replace,
163             fixups.append,
164         );
165
166         let mut actual = tt.to_string();
167         actual.push_str("\n");
168
169         expect.indent(false);
170         expect.assert_eq(&actual);
171
172         // the fixed-up tree should be syntactically valid
173         let (parse, _) = mbe::token_tree_to_syntax_node(&tt, ::mbe::TopEntryPoint::MacroItems);
174         assert_eq!(
175             parse.errors(),
176             &[],
177             "parse has syntax errors. parse tree:\n{:#?}",
178             parse.syntax_node()
179         );
180
181         reverse_fixups(&mut tt, &tmap, &fixups.undo_info);
182
183         // the fixed-up + reversed version should be equivalent to the original input
184         // (but token IDs don't matter)
185         let (original_as_tt, _) = mbe::syntax_node_to_token_tree(&parsed.syntax_node());
186         assert_eq!(tt.to_string(), original_as_tt.to_string());
187     }
188
189     #[test]
190     fn incomplete_field_expr_1() {
191         check(
192             r#"
193 fn foo() {
194     a.
195 }
196 "#,
197             expect![[r#"
198 fn foo () {a . __ra_fixup}
199 "#]],
200         )
201     }
202
203     #[test]
204     fn incomplete_field_expr_2() {
205         check(
206             r#"
207 fn foo() {
208     a. ;
209 }
210 "#,
211             expect![[r#"
212 fn foo () {a . __ra_fixup ;}
213 "#]],
214         )
215     }
216
217     #[test]
218     fn incomplete_field_expr_3() {
219         check(
220             r#"
221 fn foo() {
222     a. ;
223     bar();
224 }
225 "#,
226             expect![[r#"
227 fn foo () {a . __ra_fixup ; bar () ;}
228 "#]],
229         )
230     }
231
232     #[test]
233     fn field_expr_before_call() {
234         // another case that easily happens while typing
235         check(
236             r#"
237 fn foo() {
238     a.b
239     bar();
240 }
241 "#,
242             expect![[r#"
243 fn foo () {a . b ; bar () ;}
244 "#]],
245         )
246     }
247
248     #[test]
249     fn extraneous_comma() {
250         check(
251             r#"
252 fn foo() {
253     bar(,);
254 }
255 "#,
256             expect![[r#"
257 fn foo () {__ra_fixup ;}
258 "#]],
259         )
260     }
261 }