about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorian Diebold <flodiebold@gmail.com>2022-02-07 20:30:28 +0100
committerFlorian Diebold <flodiebold@gmail.com>2022-02-07 20:30:28 +0100
commit1b5cd03a37e5eb8acc87fd5bcee060efd735b598 (patch)
tree35e4e33f6639cb82ed355a3888e88fe872d9e722
parentc3601e9860e533c7990d90dbd773a49039bb037e (diff)
downloadrust-1b5cd03a37e5eb8acc87fd5bcee060efd735b598.tar.gz
rust-1b5cd03a37e5eb8acc87fd5bcee060efd735b598.zip
Actually check in fixup.rs
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir_expand/Cargo.toml3
-rw-r--r--crates/hir_expand/src/fixup.rs136
3 files changed, 140 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0ab9f89fd2c..11ae5c67214 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -526,6 +526,7 @@ dependencies = [
  "cfg",
  "cov-mark",
  "either",
+ "expect-test",
  "hashbrown 0.12.0",
  "itertools",
  "la-arena",
diff --git a/crates/hir_expand/Cargo.toml b/crates/hir_expand/Cargo.toml
index e5455660d3a..d7b4cbf82e2 100644
--- a/crates/hir_expand/Cargo.toml
+++ b/crates/hir_expand/Cargo.toml
@@ -27,3 +27,6 @@ profile = { path = "../profile", version = "0.0.0" }
 tt = { path = "../tt", version = "0.0.0" }
 mbe = { path = "../mbe", version = "0.0.0" }
 limit = { path = "../limit", version = "0.0.0" }
+
+[dev-dependencies]
+expect-test = "1.2.0-pre.1"
diff --git a/crates/hir_expand/src/fixup.rs b/crates/hir_expand/src/fixup.rs
new file mode 100644
index 00000000000..f82fba46e9d
--- /dev/null
+++ b/crates/hir_expand/src/fixup.rs
@@ -0,0 +1,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 () ;}
+"#]])
+    }
+}