Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use mbe::SyntheticToken;
use rustc_hash::FxHashMap;
use syntax::{
    ast::{self, AstNode},
    match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
};
use tt::{Leaf, Subtree};

#[derive(Debug)]
pub struct SyntaxFixups {
    pub append: FxHashMap<SyntaxNode, Vec<SyntheticToken>>,
    pub replace: FxHashMap<SyntaxNode, Vec<SyntheticToken>>,
}

pub fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
    let mut append = FxHashMap::default();
    let mut replace = FxHashMap::default();
    let mut preorder = node.preorder();
    while let Some(event) = preorder.next() {
        let node = match event {
            syntax::WalkEvent::Enter(node) => node,
            syntax::WalkEvent::Leave(_) => continue,
        };
        if node.kind() == SyntaxKind::ERROR {
            // TODO this might not be helpful
            replace.insert(node, Vec::new());
            preorder.skip_subtree();
            continue;
        }
        match_ast! {
            match node {
                ast::FieldExpr(it) => {
                    if it.name_ref().is_none() {
                        // incomplete field access: some_expr.|
                        append.insert(node.clone(), vec![(SyntaxKind::IDENT, "__ra_fixup".into())]);
                    }
                },
                _ => (),
            }
        }
    }
    SyntaxFixups { append, replace }
}

pub fn reverse_fixups(tt: &mut Subtree) {
    tt.token_trees.retain(|tt| match tt {
        tt::TokenTree::Leaf(Leaf::Ident(ident)) => ident.text != "__ra_fixup",
        _ => true,
    });
    tt.token_trees.iter_mut().for_each(|tt| match tt {
        tt::TokenTree::Subtree(tt) => reverse_fixups(tt),
        _ => {}
    });
}

#[cfg(test)]
mod tests {
    use expect_test::{Expect, expect};

    use super::reverse_fixups;

    #[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_censored(
            &parsed.syntax_node(),
            fixups.replace,
            fixups.append,
        );

        let mut actual = tt.to_string();
        actual.push_str("\n");

        expect.indent(false);
        expect.assert_eq(&actual);

        // the fixed-up tree should be syntactically valid
        let (parse, _) = mbe::token_tree_to_syntax_node(&tt, ::mbe::TopEntryPoint::MacroItems);
        assert_eq!(parse.errors(), &[], "parse has syntax errors. parse tree:\n{:#?}", parse.syntax_node());

        reverse_fixups(&mut tt);

        // the fixed-up + reversed version should be equivalent to the original input
        // (but token IDs don't matter)
        let (original_as_tt, _) = mbe::syntax_node_to_token_tree(&parsed.syntax_node());
        assert_eq!(tt.to_string(), original_as_tt.to_string());
    }

    #[test]
    fn incomplete_field_expr_1() {
        check(r#"
fn foo() {
    a.
}
"#, expect![[r#"
fn foo () {a . __ra_fixup}
"#]])
    }

    #[test]
    fn incomplete_field_expr_2() {
        check(r#"
fn foo() {
    a. ;
}
"#, expect![[r#"
fn foo () {a . __ra_fixup ;}
"#]])
    }

    #[test]
    fn incomplete_field_expr_3() {
        check(r#"
fn foo() {
    a. ;
    bar();
}
"#, expect![[r#"
fn foo () {a . __ra_fixup ; bar () ;}
"#]])
    }

    #[test]
    fn field_expr_before_call() {
        // another case that easily happens while typing
        check(r#"
fn foo() {
    a.b
    bar();
}
"#, expect![[r#"
fn foo () {a . b bar () ;}
"#]])
    }
}