From 0d2490f785031708867a013887337661106ff7bd Mon Sep 17 00:00:00 2001 From: BooksBaum <15612932+Booksbaum@users.noreply.github.com> Date: Mon, 9 Aug 2021 19:56:11 +0200 Subject: [PATCH] Handle tuple in macro call Only tuple name is handled (uncommented), not tuple index --- .../src/handlers/destructure_tuple_binding.rs | 369 ++++++++++++++++-- 1 file changed, 326 insertions(+), 43 deletions(-) diff --git a/crates/ide_assists/src/handlers/destructure_tuple_binding.rs b/crates/ide_assists/src/handlers/destructure_tuple_binding.rs index 4f34c4ea0df8..5694aa4c9a7e 100644 --- a/crates/ide_assists/src/handlers/destructure_tuple_binding.rs +++ b/crates/ide_assists/src/handlers/destructure_tuple_binding.rs @@ -4,10 +4,7 @@ use ide_db::{ search::{FileReference, SearchScope, UsageSearchResult}, }; use itertools::Itertools; -use syntax::{ - ast::{self, AstNode, FieldExpr, IdentPat, NameOwner}, - SyntaxNode, TextRange, -}; +use syntax::{TextRange, ast::{self, AstNode, IdentPat, NameOwner}}; use crate::assist_context::{AssistBuilder, AssistContext, Assists}; @@ -186,7 +183,7 @@ fn edit_tuple_usages( if let Some(usages) = data.usages.as_ref() { for (file_id, refs) in usages.iter() { builder.edit_file(*file_id); - + for r in refs { edit_tuple_usage(r, data, builder, ctx, in_sub_pattern); } @@ -200,12 +197,10 @@ fn edit_tuple_usage( _ctx: &AssistContext, in_sub_pattern: bool, ) { - match detect_tuple_index(usage.name.syntax(), data) { - Some((expr, idx)) => { - // index field access - let text = &data.field_names[idx]; - let range = expr.syntax().text_range(); - builder.replace(range, text); + match detect_tuple_index(usage, data) { + Some(index) => { + let text = &data.field_names[index.index]; + builder.replace(index.range, text); } None => { if in_sub_pattern { @@ -221,7 +216,12 @@ fn edit_tuple_usage( } } -fn detect_tuple_index(usage: &SyntaxNode, data: &TupleData) -> Option<(FieldExpr, usize)> { +struct TupleIndex { + index: usize, + range: TextRange, +} + +fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option { // usage is IDENT // IDENT // NAME_REF @@ -230,7 +230,9 @@ fn detect_tuple_index(usage: &SyntaxNode, data: &TupleData) -> Option<(FieldExpr // PATH_EXPR // PAREN_EXRP* // FIELD_EXPR - let node = usage + + let node = + usage.name.syntax() .ancestors() .skip_while(|s| !ast::PathExpr::can_cast(s.kind())) .skip(1) // PATH_EXPR @@ -239,7 +241,29 @@ fn detect_tuple_index(usage: &SyntaxNode, data: &TupleData) -> Option<(FieldExpr if let Some(field_expr) = ast::FieldExpr::cast(node) { let idx = field_expr.name_ref()?.as_tuple_field()?; if idx < data.field_names.len() { - Some((field_expr, idx)) + + // special case: in macro call -> range of `field_expr` in applied macro, NOT range in actual file! + if field_expr.syntax().ancestors().any(|a| ast::MacroStmts::can_cast(a.kind())) { + cov_mark::hit!(destructure_tuple_macro_call); + + // issue: cannot differentiate between tuple index passed into macro or tuple index as result of macro: + // ```rust + // macro_rules! m { + // ($t1:expr, $t2:expr) => { $t1; $t2.0 } + // } + // let t = (1,2); + // m!(t.0, t) + // ``` + // -> 2 tuple index usages detected! + // + // -> only handle `t` + return None; + } + + Some(TupleIndex { + index: idx, + range: field_expr.syntax().text_range(), + }) } else { // tuple index out of range None @@ -1030,32 +1054,7 @@ fn main { ) } - // ----------------------- - #[test] - #[ignore] - fn replace_in_macro_call() { - // doesn't work: cannot find usage in `println!` - // Note: Just `t` works, but not `t.0` - check_assist( - assist, - r#" -fn main() { - let $0t = (1,2); - println!("{}", t.0); -} - "#, - r#" -fn main() { - let ($0_0, _1) = (1,2); - println!("{}", _0); -} - "#, - ) - } - - /// Tests for destructure of tuple in sub-pattern: - /// `let $0t = (1,2);` -> `let t @ (_0, _1) = (1,2);` - mod sub_pattern { + mod assist { use super::*; use crate::tests::check_assist_by_label; @@ -1063,18 +1062,48 @@ fn main() { destructure_tuple_binding_impl(acc, ctx, true) } - fn check_sub_pattern_assist( + pub(crate) fn check_in_place_assist( + ra_fixture_before: &str, ra_fixture_after: &str, ) { - // with sub-pattern there are two assists: One with and one without sub-pattern check_assist_by_label( assist, ra_fixture_before, ra_fixture_after, - "Destructure tuple in sub-pattern") + "Destructure tuple in place" + ); } + pub(crate) fn check_sub_pattern_assist( + ra_fixture_before: &str, + ra_fixture_after: &str, + ) { + check_assist_by_label( + assist, + ra_fixture_before, + ra_fixture_after, + "Destructure tuple in sub-pattern" + ); + } + + pub(crate) fn check_both_assists( + ra_fixture_before: &str, + ra_fixture_after_in_place: &str, + ra_fixture_after_in_sub_pattern: &str, + ) { + check_in_place_assist(ra_fixture_before, ra_fixture_after_in_place); + check_sub_pattern_assist(ra_fixture_before, ra_fixture_after_in_sub_pattern); + } + } + + /// Tests for destructure of tuple in sub-pattern: + /// `let $0t = (1,2);` -> `let t @ (_0, _1) = (1,2);` + mod sub_pattern { + use super::*; + use super::assist::*; + use crate::tests::check_assist_by_label; + #[test] fn destructure_in_sub_pattern() { check_sub_pattern_assist( @@ -1097,6 +1126,9 @@ fn main() { #[test] fn trigger_both_destructure_tuple_assists() { + fn assist(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + destructure_tuple_binding_impl(acc, ctx, true) + } let text = r#" fn main() { let $0t = (1,2); @@ -1259,4 +1291,255 @@ fn main() { ) } } + + /// Tests for tuple usage in macro call: + /// `dbg!(t.0)` + mod in_macro_call { + use super::assist::*; + + + #[test] + fn detect_macro_call() { + cov_mark::check!(destructure_tuple_macro_call); + check_in_place_assist( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t.0); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/.0); +} + "#, + ) + } + + #[test] + fn tuple_usage() { + check_both_assists( + // leading `"foo"` to ensure `$e` doesn't start at position `0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t); +} + "#, + ) + } + + #[test] + fn tuple_function_usage() { + check_both_assists( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t.into()); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/.into()); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t.into()); +} + "#, + ) + } + + #[test] + fn tuple_index_usage() { + check_both_assists( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t.0); +} + "#, + // FIXME: replace `t.0` with `_0` (cannot detect range of tuple index in macro call) + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/.0); +} + "#, + // FIXME: replace `t.0` with `_0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t.0); +} + "#, + ) + } + + #[test] + fn tuple_in_parentheses_index_usage() { + check_both_assists( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!((t).0); +} + "#, + // FIXME: replace `(t).0` with `_0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!((/*t*/).0); +} + "#, + // FIXME: replace `(t).0` with `_0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!((t).0); +} + "#, + ) + } + + #[test] + fn empty_macro() { + check_in_place_assist( + r#" +macro_rules! m { + () => { "foo" }; + ($e:expr) => { $e; "foo" }; +} + +fn main() { + let $0t = (1,2); + m!(t); +} + "#, + // FIXME: macro allows no arg -> is valid. But assist should result in invalid code + r#" +macro_rules! m { + () => { "foo" }; + ($e:expr) => { $e; "foo" }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/); +} + "#, + ) + } + + #[test] + fn tuple_index_in_macro() { + check_both_assists( + r#" +macro_rules! m { + ($t:expr, $i:expr) => { $t.0 + $i }; +} + +fn main() { + let $0t = (1,2); + m!(t, t.0); +} + "#, + // FIXME: replace `t.0` in macro call (not IN macro) with `_0` + r#" +macro_rules! m { + ($t:expr, $i:expr) => { $t.0 + $i }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/, /*t*/.0); +} + "#, + // FIXME: replace `t.0` in macro call with `_0` + r#" +macro_rules! m { + ($t:expr, $i:expr) => { $t.0 + $i }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t, t.0); +} + "#, + ) + + } + } }