ide_assists/handlers/
replace_arith_op.rs

1use ide_db::assists::{AssistId, GroupLabel};
2use syntax::{
3    AstNode,
4    ast::{self, ArithOp, BinaryOp, syntax_factory::SyntaxFactory},
5};
6
7use crate::assist_context::{AssistContext, Assists};
8
9// Assist: replace_arith_with_checked
10//
11// Replaces arithmetic on integers with the `checked_*` equivalent.
12//
13// ```
14// fn main() {
15//   let x = 1 $0+ 2;
16// }
17// ```
18// ->
19// ```
20// fn main() {
21//   let x = 1.checked_add(2);
22// }
23// ```
24pub(crate) fn replace_arith_with_checked(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25    replace_arith(acc, ctx, ArithKind::Checked)
26}
27
28// Assist: replace_arith_with_saturating
29//
30// Replaces arithmetic on integers with the `saturating_*` equivalent.
31//
32// ```
33// fn main() {
34//   let x = 1 $0+ 2;
35// }
36// ```
37// ->
38// ```
39// fn main() {
40//   let x = 1.saturating_add(2);
41// }
42// ```
43pub(crate) fn replace_arith_with_saturating(
44    acc: &mut Assists,
45    ctx: &AssistContext<'_>,
46) -> Option<()> {
47    replace_arith(acc, ctx, ArithKind::Saturating)
48}
49
50// Assist: replace_arith_with_wrapping
51//
52// Replaces arithmetic on integers with the `wrapping_*` equivalent.
53//
54// ```
55// fn main() {
56//   let x = 1 $0+ 2;
57// }
58// ```
59// ->
60// ```
61// fn main() {
62//   let x = 1.wrapping_add(2);
63// }
64// ```
65pub(crate) fn replace_arith_with_wrapping(
66    acc: &mut Assists,
67    ctx: &AssistContext<'_>,
68) -> Option<()> {
69    replace_arith(acc, ctx, ArithKind::Wrapping)
70}
71
72fn replace_arith(acc: &mut Assists, ctx: &AssistContext<'_>, kind: ArithKind) -> Option<()> {
73    let (lhs, op, rhs) = parse_binary_op(ctx)?;
74    let op_expr = lhs.syntax().parent()?;
75
76    if !is_primitive_int(ctx, &lhs) || !is_primitive_int(ctx, &rhs) {
77        return None;
78    }
79
80    acc.add_group(
81        &GroupLabel("Replace arithmetic...".into()),
82        kind.assist_id(),
83        kind.label(),
84        op_expr.text_range(),
85        |builder| {
86            let mut edit = builder.make_editor(rhs.syntax());
87            let make = SyntaxFactory::with_mappings();
88            let method_name = kind.method_name(op);
89
90            let needs_parentheses =
91                lhs.precedence().needs_parentheses_in(ast::prec::ExprPrecedence::Postfix);
92            let receiver = if needs_parentheses { make.expr_paren(lhs).into() } else { lhs };
93            let arith_expr =
94                make.expr_method_call(receiver, make.name_ref(&method_name), make.arg_list([rhs]));
95            edit.replace(op_expr, arith_expr.syntax());
96
97            edit.add_mappings(make.finish_with_mappings());
98            builder.add_file_edits(ctx.vfs_file_id(), edit);
99        },
100    )
101}
102
103fn is_primitive_int(ctx: &AssistContext<'_>, expr: &ast::Expr) -> bool {
104    match ctx.sema.type_of_expr(expr) {
105        Some(ty) => ty.adjusted().is_int_or_uint(),
106        _ => false,
107    }
108}
109
110/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`)
111fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(ast::Expr, ArithOp, ast::Expr)> {
112    if !ctx.has_empty_selection() {
113        return None;
114    }
115    let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
116
117    let op = match expr.op_kind() {
118        Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add,
119        Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub,
120        Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul,
121        Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div,
122        _ => return None,
123    };
124
125    let lhs = expr.lhs()?;
126    let rhs = expr.rhs()?;
127
128    Some((lhs, op, rhs))
129}
130
131pub(crate) enum ArithKind {
132    Saturating,
133    Wrapping,
134    Checked,
135}
136
137impl ArithKind {
138    fn assist_id(&self) -> AssistId {
139        let s = match self {
140            ArithKind::Saturating => "replace_arith_with_saturating",
141            ArithKind::Checked => "replace_arith_with_checked",
142            ArithKind::Wrapping => "replace_arith_with_wrapping",
143        };
144
145        AssistId::refactor_rewrite(s)
146    }
147
148    fn label(&self) -> &'static str {
149        match self {
150            ArithKind::Saturating => "Replace arithmetic with call to saturating_*",
151            ArithKind::Checked => "Replace arithmetic with call to checked_*",
152            ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*",
153        }
154    }
155
156    fn method_name(&self, op: ArithOp) -> String {
157        let prefix = match self {
158            ArithKind::Checked => "checked_",
159            ArithKind::Wrapping => "wrapping_",
160            ArithKind::Saturating => "saturating_",
161        };
162
163        let suffix = match op {
164            ArithOp::Add => "add",
165            ArithOp::Sub => "sub",
166            ArithOp::Mul => "mul",
167            ArithOp::Div => "div",
168            _ => unreachable!("this function should only be called with +, -, / or *"),
169        };
170        format!("{prefix}{suffix}")
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use crate::tests::{check_assist, check_assist_not_applicable};
177
178    use super::*;
179
180    #[test]
181    fn arith_kind_method_name() {
182        assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add");
183        assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub");
184    }
185
186    #[test]
187    fn replace_arith_with_checked_add() {
188        check_assist(
189            replace_arith_with_checked,
190            r#"
191fn main() {
192    let x = 1 $0+ 2;
193}
194"#,
195            r#"
196fn main() {
197    let x = 1.checked_add(2);
198}
199"#,
200        )
201    }
202
203    #[test]
204    fn replace_arith_with_saturating_add() {
205        check_assist(
206            replace_arith_with_saturating,
207            r#"
208fn main() {
209    let x = 1 $0+ 2;
210}
211"#,
212            r#"
213fn main() {
214    let x = 1.saturating_add(2);
215}
216"#,
217        )
218    }
219
220    #[test]
221    fn replace_arith_with_wrapping_add() {
222        check_assist(
223            replace_arith_with_wrapping,
224            r#"
225fn main() {
226    let x = 1 $0+ 2;
227}
228"#,
229            r#"
230fn main() {
231    let x = 1.wrapping_add(2);
232}
233"#,
234        )
235    }
236
237    #[test]
238    fn replace_arith_with_wrapping_add_add_parenthesis() {
239        check_assist(
240            replace_arith_with_wrapping,
241            r#"
242fn main() {
243    let x = 1*3 $0+ 2;
244}
245"#,
246            r#"
247fn main() {
248    let x = (1*3).wrapping_add(2);
249}
250"#,
251        )
252    }
253
254    #[test]
255    fn replace_arith_not_applicable_with_non_empty_selection() {
256        check_assist_not_applicable(
257            replace_arith_with_checked,
258            r#"
259fn main() {
260    let x = 1 $0+$0 2;
261}
262"#,
263        )
264    }
265}