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