ide_assists/handlers/
unwrap_tuple.rs

1use syntax::{
2    AstNode, T,
3    ast::{self, edit::AstNodeEdit},
4};
5
6use crate::{AssistContext, AssistId, Assists};
7
8// Assist: unwrap_tuple
9//
10// Unwrap the tuple to different variables.
11//
12// ```
13// # //- minicore: result
14// fn main() {
15//     $0let (foo, bar) = ("Foo", "Bar");
16// }
17// ```
18// ->
19// ```
20// fn main() {
21//     let foo = "Foo";
22//     let bar = "Bar";
23// }
24// ```
25pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
26    let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
27    let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
28    let indent_level = let_stmt.indent_level().0 as usize;
29    let pat = let_stmt.pat()?;
30    let ty = let_stmt.ty();
31    let init = let_stmt.initializer()?;
32
33    // This only applies for tuple patterns, types, and initializers.
34    let tuple_pat = match pat {
35        ast::Pat::TuplePat(pat) => pat,
36        _ => return None,
37    };
38    let tuple_ty = ty.and_then(|it| match it {
39        ast::Type::TupleType(ty) => Some(ty),
40        _ => None,
41    });
42    let tuple_init = match init {
43        ast::Expr::TupleExpr(expr) => expr,
44        _ => return None,
45    };
46
47    if tuple_pat.fields().count() != tuple_init.fields().count() {
48        return None;
49    }
50    if let Some(tys) = &tuple_ty
51        && tuple_pat.fields().count() != tys.fields().count()
52    {
53        return None;
54    }
55
56    let parent = let_kw.parent()?;
57
58    acc.add(
59        AssistId::refactor_rewrite("unwrap_tuple"),
60        "Unwrap tuple",
61        let_kw.text_range(),
62        |edit| {
63            let indents = "    ".repeat(indent_level);
64
65            // If there is an ascribed type, insert that type for each declaration,
66            // otherwise, omit that type.
67            if let Some(tys) = tuple_ty {
68                let mut zipped_decls = String::new();
69                for (pat, ty, expr) in
70                    itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())
71                {
72                    zipped_decls.push_str(&format!("{indents}let {pat}: {ty} = {expr};\n"))
73                }
74                edit.replace(parent.text_range(), zipped_decls.trim());
75            } else {
76                let mut zipped_decls = String::new();
77                for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) {
78                    zipped_decls.push_str(&format!("{indents}let {pat} = {expr};\n"));
79                }
80                edit.replace(parent.text_range(), zipped_decls.trim());
81            }
82        },
83    )
84}
85
86#[cfg(test)]
87mod tests {
88    use crate::tests::check_assist;
89
90    use super::*;
91
92    #[test]
93    fn unwrap_tuples() {
94        check_assist(
95            unwrap_tuple,
96            r#"
97fn main() {
98    $0let (foo, bar) = ("Foo", "Bar");
99}
100"#,
101            r#"
102fn main() {
103    let foo = "Foo";
104    let bar = "Bar";
105}
106"#,
107        );
108
109        check_assist(
110            unwrap_tuple,
111            r#"
112fn main() {
113    $0let (foo, bar, baz) = ("Foo", "Bar", "Baz");
114}
115"#,
116            r#"
117fn main() {
118    let foo = "Foo";
119    let bar = "Bar";
120    let baz = "Baz";
121}
122"#,
123        );
124    }
125
126    #[test]
127    fn unwrap_tuple_with_types() {
128        check_assist(
129            unwrap_tuple,
130            r#"
131fn main() {
132    $0let (foo, bar): (u8, i32) = (5, 10);
133}
134"#,
135            r#"
136fn main() {
137    let foo: u8 = 5;
138    let bar: i32 = 10;
139}
140"#,
141        );
142
143        check_assist(
144            unwrap_tuple,
145            r#"
146fn main() {
147    $0let (foo, bar, baz): (u8, i32, f64) = (5, 10, 17.5);
148}
149"#,
150            r#"
151fn main() {
152    let foo: u8 = 5;
153    let bar: i32 = 10;
154    let baz: f64 = 17.5;
155}
156"#,
157        );
158    }
159}