ide_assists/handlers/
replace_arith_op.rs1use 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
9pub(crate) fn replace_arith_with_checked(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25 replace_arith(acc, ctx, ArithKind::Checked)
26}
27
28pub(crate) fn replace_arith_with_saturating(
44 acc: &mut Assists,
45 ctx: &AssistContext<'_>,
46) -> Option<()> {
47 replace_arith(acc, ctx, ArithKind::Saturating)
48}
49
50pub(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
110fn 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}