]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/syntax/src/parsing/reparsing.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / syntax / src / parsing / reparsing.rs
1 //! Implementation of incremental re-parsing.
2 //!
3 //! We use two simple strategies for this:
4 //! - if the edit modifies only a single token (like changing an identifier's
5 //! letter), we replace only this token.
6 //! - otherwise, we search for the nearest `{}` block which contains the edit
7 //! and try to parse only this block.
8
9 use parser::Reparser;
10 use text_edit::Indel;
11
12 use crate::{
13 parsing::build_tree,
14 syntax_node::{GreenNode, GreenToken, NodeOrToken, SyntaxElement, SyntaxNode},
15 SyntaxError,
16 SyntaxKind::*,
17 TextRange, TextSize, T,
18 };
19
20 pub(crate) fn incremental_reparse(
21 node: &SyntaxNode,
22 edit: &Indel,
23 errors: Vec<SyntaxError>,
24 ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
25 if let Some((green, new_errors, old_range)) = reparse_token(node, edit) {
26 return Some((green, merge_errors(errors, new_errors, old_range, edit), old_range));
27 }
28
29 if let Some((green, new_errors, old_range)) = reparse_block(node, edit) {
30 return Some((green, merge_errors(errors, new_errors, old_range, edit), old_range));
31 }
32 None
33 }
34
35 fn reparse_token(
36 root: &SyntaxNode,
37 edit: &Indel,
38 ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
39 let prev_token = root.covering_element(edit.delete).as_token()?.clone();
40 let prev_token_kind = prev_token.kind();
41 match prev_token_kind {
42 WHITESPACE | COMMENT | IDENT | STRING => {
43 if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT {
44 // removing a new line may extends previous token
45 let deleted_range = edit.delete - prev_token.text_range().start();
46 if prev_token.text()[deleted_range].contains('\n') {
47 return None;
48 }
49 }
50
51 let mut new_text = get_text_after_edit(prev_token.clone().into(), edit);
52 let (new_token_kind, new_err) = parser::LexedStr::single_token(&new_text)?;
53
54 if new_token_kind != prev_token_kind
55 || (new_token_kind == IDENT && is_contextual_kw(&new_text))
56 {
57 return None;
58 }
59
60 // Check that edited token is not a part of the bigger token.
61 // E.g. if for source code `bruh"str"` the user removed `ruh`, then
62 // `b` no longer remains an identifier, but becomes a part of byte string literal
63 if let Some(next_char) = root.text().char_at(prev_token.text_range().end()) {
64 new_text.push(next_char);
65 let token_with_next_char = parser::LexedStr::single_token(&new_text);
66 if let Some((_kind, _error)) = token_with_next_char {
67 return None;
68 }
69 new_text.pop();
70 }
71
72 let new_token = GreenToken::new(rowan::SyntaxKind(prev_token_kind.into()), &new_text);
73 let range = TextRange::up_to(TextSize::of(&new_text));
74 Some((
75 prev_token.replace_with(new_token),
76 new_err.into_iter().map(|msg| SyntaxError::new(msg, range)).collect(),
77 prev_token.text_range(),
78 ))
79 }
80 _ => None,
81 }
82 }
83
84 fn reparse_block(
85 root: &SyntaxNode,
86 edit: &Indel,
87 ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
88 let (node, reparser) = find_reparsable_node(root, edit.delete)?;
89 let text = get_text_after_edit(node.clone().into(), edit);
90
91 let lexed = parser::LexedStr::new(text.as_str());
92 let parser_input = lexed.to_input();
93 if !is_balanced(&lexed) {
94 return None;
95 }
96
97 let tree_traversal = reparser.parse(&parser_input);
98
99 let (green, new_parser_errors, _eof) = build_tree(lexed, tree_traversal);
100
101 Some((node.replace_with(green), new_parser_errors, node.text_range()))
102 }
103
104 fn get_text_after_edit(element: SyntaxElement, edit: &Indel) -> String {
105 let edit = Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
106
107 let mut text = match element {
108 NodeOrToken::Token(token) => token.text().to_string(),
109 NodeOrToken::Node(node) => node.text().to_string(),
110 };
111 edit.apply(&mut text);
112 text
113 }
114
115 fn is_contextual_kw(text: &str) -> bool {
116 matches!(text, "auto" | "default" | "union")
117 }
118
119 fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(SyntaxNode, Reparser)> {
120 let node = node.covering_element(range);
121
122 node.ancestors().find_map(|node| {
123 let first_child = node.first_child_or_token().map(|it| it.kind());
124 let parent = node.parent().map(|it| it.kind());
125 Reparser::for_node(node.kind(), first_child, parent).map(|r| (node, r))
126 })
127 }
128
129 fn is_balanced(lexed: &parser::LexedStr<'_>) -> bool {
130 if lexed.is_empty() || lexed.kind(0) != T!['{'] || lexed.kind(lexed.len() - 1) != T!['}'] {
131 return false;
132 }
133 let mut balance = 0usize;
134 for i in 1..lexed.len() - 1 {
135 match lexed.kind(i) {
136 T!['{'] => balance += 1,
137 T!['}'] => {
138 balance = match balance.checked_sub(1) {
139 Some(b) => b,
140 None => return false,
141 }
142 }
143 _ => (),
144 }
145 }
146 balance == 0
147 }
148
149 fn merge_errors(
150 old_errors: Vec<SyntaxError>,
151 new_errors: Vec<SyntaxError>,
152 range_before_reparse: TextRange,
153 edit: &Indel,
154 ) -> Vec<SyntaxError> {
155 let mut res = Vec::new();
156
157 for old_err in old_errors {
158 let old_err_range = old_err.range();
159 if old_err_range.end() <= range_before_reparse.start() {
160 res.push(old_err);
161 } else if old_err_range.start() >= range_before_reparse.end() {
162 let inserted_len = TextSize::of(&edit.insert);
163 res.push(old_err.with_range((old_err_range + inserted_len) - edit.delete.len()));
164 // Note: extra parens are intentional to prevent uint underflow, HWAB (here was a bug)
165 }
166 }
167 res.extend(new_errors.into_iter().map(|new_err| {
168 // fighting borrow checker with a variable ;)
169 let offseted_range = new_err.range() + range_before_reparse.start();
170 new_err.with_range(offseted_range)
171 }));
172 res
173 }
174
175 #[cfg(test)]
176 mod tests {
177 use test_utils::{assert_eq_text, extract_range};
178
179 use super::*;
180 use crate::{AstNode, Parse, SourceFile};
181
182 fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
183 let (range, before) = extract_range(before);
184 let edit = Indel::replace(range, replace_with.to_owned());
185 let after = {
186 let mut after = before.clone();
187 edit.apply(&mut after);
188 after
189 };
190
191 let fully_reparsed = SourceFile::parse(&after);
192 let incrementally_reparsed: Parse<SourceFile> = {
193 let before = SourceFile::parse(&before);
194 let (green, new_errors, range) =
195 incremental_reparse(before.tree().syntax(), &edit, before.errors.to_vec()).unwrap();
196 assert_eq!(range.len(), reparsed_len.into(), "reparsed fragment has wrong length");
197 Parse::new(green, new_errors)
198 };
199
200 assert_eq_text!(
201 &format!("{:#?}", fully_reparsed.tree().syntax()),
202 &format!("{:#?}", incrementally_reparsed.tree().syntax()),
203 );
204 assert_eq!(fully_reparsed.errors(), incrementally_reparsed.errors());
205 }
206
207 #[test] // FIXME: some test here actually test token reparsing
208 fn reparse_block_tests() {
209 do_check(
210 r"
211 fn foo() {
212 let x = foo + $0bar$0
213 }
214 ",
215 "baz",
216 3,
217 );
218 do_check(
219 r"
220 fn foo() {
221 let x = foo$0 + bar$0
222 }
223 ",
224 "baz",
225 25,
226 );
227 do_check(
228 r"
229 struct Foo {
230 f: foo$0$0
231 }
232 ",
233 ",\n g: (),",
234 14,
235 );
236 do_check(
237 r"
238 fn foo {
239 let;
240 1 + 1;
241 $092$0;
242 }
243 ",
244 "62",
245 31, // FIXME: reparse only int literal here
246 );
247 do_check(
248 r"
249 mod foo {
250 fn $0$0
251 }
252 ",
253 "bar",
254 11,
255 );
256
257 do_check(
258 r"
259 trait Foo {
260 type $0Foo$0;
261 }
262 ",
263 "Output",
264 3,
265 );
266 do_check(
267 r"
268 impl IntoIterator<Item=i32> for Foo {
269 f$0$0
270 }
271 ",
272 "n next(",
273 9,
274 );
275 do_check(r"use a::b::{foo,$0,bar$0};", "baz", 10);
276 do_check(
277 r"
278 pub enum A {
279 Foo$0$0
280 }
281 ",
282 "\nBar;\n",
283 11,
284 );
285 do_check(
286 r"
287 foo!{a, b$0$0 d}
288 ",
289 ", c[3]",
290 8,
291 );
292 do_check(
293 r"
294 fn foo() {
295 vec![$0$0]
296 }
297 ",
298 "123",
299 14,
300 );
301 do_check(
302 r"
303 extern {
304 fn$0;$0
305 }
306 ",
307 " exit(code: c_int)",
308 11,
309 );
310 }
311
312 #[test]
313 fn reparse_token_tests() {
314 do_check(
315 r"$0$0
316 fn foo() -> i32 { 1 }
317 ",
318 "\n\n\n \n",
319 1,
320 );
321 do_check(
322 r"
323 fn foo() -> $0$0 {}
324 ",
325 " \n",
326 2,
327 );
328 do_check(
329 r"
330 fn $0foo$0() -> i32 { 1 }
331 ",
332 "bar",
333 3,
334 );
335 do_check(
336 r"
337 fn foo$0$0foo() { }
338 ",
339 "bar",
340 6,
341 );
342 do_check(
343 r"
344 fn foo /* $0$0 */ () {}
345 ",
346 "some comment",
347 6,
348 );
349 do_check(
350 r"
351 fn baz $0$0 () {}
352 ",
353 " \t\t\n\n",
354 2,
355 );
356 do_check(
357 r"
358 fn baz $0$0 () {}
359 ",
360 " \t\t\n\n",
361 2,
362 );
363 do_check(
364 r"
365 /// foo $0$0omment
366 mod { }
367 ",
368 "c",
369 14,
370 );
371 do_check(
372 r#"
373 fn -> &str { "Hello$0$0" }
374 "#,
375 ", world",
376 7,
377 );
378 do_check(
379 r#"
380 fn -> &str { // "Hello$0$0"
381 "#,
382 ", world",
383 10,
384 );
385 do_check(
386 r##"
387 fn -> &str { r#"Hello$0$0"#
388 "##,
389 ", world",
390 10,
391 );
392 do_check(
393 r"
394 #[derive($0Copy$0)]
395 enum Foo {
396
397 }
398 ",
399 "Clone",
400 4,
401 );
402 }
403
404 #[test]
405 fn reparse_str_token_with_error_unchanged() {
406 do_check(r#""$0Unclosed$0 string literal"#, "Still unclosed", 24);
407 }
408
409 #[test]
410 fn reparse_str_token_with_error_fixed() {
411 do_check(r#""unterinated$0$0"#, "\"", 12);
412 }
413
414 #[test]
415 fn reparse_block_with_error_in_middle_unchanged() {
416 do_check(
417 r#"fn main() {
418 if {}
419 32 + 4$0$0
420 return
421 if {}
422 }"#,
423 "23",
424 105,
425 )
426 }
427
428 #[test]
429 fn reparse_block_with_error_in_middle_fixed() {
430 do_check(
431 r#"fn main() {
432 if {}
433 32 + 4$0$0
434 return
435 if {}
436 }"#,
437 ";",
438 105,
439 )
440 }
441 }