From 1b5cd03a37e5eb8acc87fd5bcee060efd735b598 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Mon, 7 Feb 2022 20:30:28 +0100 Subject: [PATCH] Actually check in fixup.rs --- Cargo.lock | 1 + crates/hir_expand/Cargo.toml | 3 + crates/hir_expand/src/fixup.rs | 136 +++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 crates/hir_expand/src/fixup.rs 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>, + pub replace: FxHashMap>, +} + +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 () ;} +"#]]) + } +}