Skip to main content

ide_assists/handlers/
unwrap_tuple.rs

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