Skip to content

Commit 814ff01

Browse files
committed
Auto merge of rust-lang#13458 - cameron1024:suggest-checked-wrapping-saturating, r=Veykril
add wrapping/checked/saturating assist This addresses rust-lang#13452 I'm not sure about the structure of the code. I'm not sure if it needs to be 3 separate assists, and if that means it needs to be in 3 separate files as well. Most of the logic is in `util.rs`, which feels funny to me, but there seems to be a pattern of 1 assist per file, and this seems better than duplicating the logic. Let me know if anything needs changes 😁
2 parents 25717af + 0dd2682 commit 814ff01

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed

crates/hir/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2926,6 +2926,13 @@ impl Type {
29262926
matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Uint(UintTy::Usize)))
29272927
}
29282928

2929+
pub fn is_int_or_uint(&self) -> bool {
2930+
match self.ty.kind(Interner) {
2931+
TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)) => true,
2932+
_ => false,
2933+
}
2934+
}
2935+
29292936
pub fn remove_ref(&self) -> Option<Type> {
29302937
match &self.ty.kind(Interner) {
29312938
TyKind::Ref(.., ty) => Some(self.derived(ty.clone())),
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
use ide_db::assists::{AssistId, AssistKind, GroupLabel};
2+
use syntax::{
3+
ast::{self, ArithOp, BinaryOp},
4+
AstNode, TextRange,
5+
};
6+
7+
use 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+
// ```
24+
pub(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+
// ```
43+
pub(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+
// ```
65+
pub(crate) fn replace_arith_with_wrapping(
66+
acc: &mut Assists,
67+
ctx: &AssistContext<'_>,
68+
) -> Option<()> {
69+
replace_arith(acc, ctx, ArithKind::Wrapping)
70+
}
71+
72+
fn replace_arith(acc: &mut Assists, ctx: &AssistContext<'_>, kind: ArithKind) -> Option<()> {
73+
let (lhs, op, rhs) = parse_binary_op(ctx)?;
74+
75+
if !is_primitive_int(ctx, &lhs) || !is_primitive_int(ctx, &rhs) {
76+
return None;
77+
}
78+
79+
let start = lhs.syntax().text_range().start();
80+
let end = rhs.syntax().text_range().end();
81+
let range = TextRange::new(start, end);
82+
83+
acc.add_group(
84+
&GroupLabel("replace_arith".into()),
85+
kind.assist_id(),
86+
kind.label(),
87+
range,
88+
|builder| {
89+
let method_name = kind.method_name(op);
90+
91+
builder.replace(range, format!("{lhs}.{method_name}({rhs})"))
92+
},
93+
)
94+
}
95+
96+
fn is_primitive_int(ctx: &AssistContext<'_>, expr: &ast::Expr) -> bool {
97+
match ctx.sema.type_of_expr(expr) {
98+
Some(ty) => ty.adjusted().is_int_or_uint(),
99+
_ => false,
100+
}
101+
}
102+
103+
/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`)
104+
fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(ast::Expr, ArithOp, ast::Expr)> {
105+
let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
106+
107+
let op = match expr.op_kind() {
108+
Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add,
109+
Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub,
110+
Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul,
111+
Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div,
112+
_ => return None,
113+
};
114+
115+
let lhs = expr.lhs()?;
116+
let rhs = expr.rhs()?;
117+
118+
Some((lhs, op, rhs))
119+
}
120+
121+
pub(crate) enum ArithKind {
122+
Saturating,
123+
Wrapping,
124+
Checked,
125+
}
126+
127+
impl ArithKind {
128+
fn assist_id(&self) -> AssistId {
129+
let s = match self {
130+
ArithKind::Saturating => "replace_arith_with_saturating",
131+
ArithKind::Checked => "replace_arith_with_checked",
132+
ArithKind::Wrapping => "replace_arith_with_wrapping",
133+
};
134+
135+
AssistId(s, AssistKind::RefactorRewrite)
136+
}
137+
138+
fn label(&self) -> &'static str {
139+
match self {
140+
ArithKind::Saturating => "Replace arithmetic with call to saturating_*",
141+
ArithKind::Checked => "Replace arithmetic with call to checked_*",
142+
ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*",
143+
}
144+
}
145+
146+
fn method_name(&self, op: ArithOp) -> String {
147+
let prefix = match self {
148+
ArithKind::Checked => "checked_",
149+
ArithKind::Wrapping => "wrapping_",
150+
ArithKind::Saturating => "saturating_",
151+
};
152+
153+
let suffix = match op {
154+
ArithOp::Add => "add",
155+
ArithOp::Sub => "sub",
156+
ArithOp::Mul => "mul",
157+
ArithOp::Div => "div",
158+
_ => unreachable!("this function should only be called with +, -, / or *"),
159+
};
160+
format!("{prefix}{suffix}")
161+
}
162+
}
163+
164+
#[cfg(test)]
165+
mod tests {
166+
use crate::tests::check_assist;
167+
168+
use super::*;
169+
170+
#[test]
171+
fn arith_kind_method_name() {
172+
assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add");
173+
assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub");
174+
}
175+
176+
#[test]
177+
fn replace_arith_with_checked_add() {
178+
check_assist(
179+
replace_arith_with_checked,
180+
r#"
181+
fn main() {
182+
let x = 1 $0+ 2;
183+
}
184+
"#,
185+
r#"
186+
fn main() {
187+
let x = 1.checked_add(2);
188+
}
189+
"#,
190+
)
191+
}
192+
193+
#[test]
194+
fn replace_arith_with_saturating_add() {
195+
check_assist(
196+
replace_arith_with_saturating,
197+
r#"
198+
fn main() {
199+
let x = 1 $0+ 2;
200+
}
201+
"#,
202+
r#"
203+
fn main() {
204+
let x = 1.saturating_add(2);
205+
}
206+
"#,
207+
)
208+
}
209+
210+
#[test]
211+
fn replace_arith_with_wrapping_add() {
212+
check_assist(
213+
replace_arith_with_wrapping,
214+
r#"
215+
fn main() {
216+
let x = 1 $0+ 2;
217+
}
218+
"#,
219+
r#"
220+
fn main() {
221+
let x = 1.wrapping_add(2);
222+
}
223+
"#,
224+
)
225+
}
226+
}

crates/ide-assists/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ mod handlers {
186186
mod replace_derive_with_manual_impl;
187187
mod replace_if_let_with_match;
188188
mod replace_or_with_or_else;
189+
mod replace_arith_op;
189190
mod introduce_named_generic;
190191
mod replace_let_with_if_let;
191192
mod replace_qualified_name_with_use;
@@ -293,6 +294,9 @@ mod handlers {
293294
replace_or_with_or_else::replace_or_with_or_else,
294295
replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
295296
replace_qualified_name_with_use::replace_qualified_name_with_use,
297+
replace_arith_op::replace_arith_with_wrapping,
298+
replace_arith_op::replace_arith_with_checked,
299+
replace_arith_op::replace_arith_with_saturating,
296300
sort_items::sort_items,
297301
split_import::split_import,
298302
toggle_ignore::toggle_ignore,

crates/ide-assists/src/tests/generated.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,57 @@ impl Foo for Bar {
20662066
)
20672067
}
20682068

2069+
#[test]
2070+
fn doctest_replace_arith_with_checked() {
2071+
check_doc_test(
2072+
"replace_arith_with_checked",
2073+
r#####"
2074+
fn main() {
2075+
let x = 1 $0+ 2;
2076+
}
2077+
"#####,
2078+
r#####"
2079+
fn main() {
2080+
let x = 1.checked_add(2);
2081+
}
2082+
"#####,
2083+
)
2084+
}
2085+
2086+
#[test]
2087+
fn doctest_replace_arith_with_saturating() {
2088+
check_doc_test(
2089+
"replace_arith_with_saturating",
2090+
r#####"
2091+
fn main() {
2092+
let x = 1 $0+ 2;
2093+
}
2094+
"#####,
2095+
r#####"
2096+
fn main() {
2097+
let x = 1.saturating_add(2);
2098+
}
2099+
"#####,
2100+
)
2101+
}
2102+
2103+
#[test]
2104+
fn doctest_replace_arith_with_wrapping() {
2105+
check_doc_test(
2106+
"replace_arith_with_wrapping",
2107+
r#####"
2108+
fn main() {
2109+
let x = 1 $0+ 2;
2110+
}
2111+
"#####,
2112+
r#####"
2113+
fn main() {
2114+
let x = 1.wrapping_add(2);
2115+
}
2116+
"#####,
2117+
)
2118+
}
2119+
20692120
#[test]
20702121
fn doctest_replace_char_with_string() {
20712122
check_doc_test(

0 commit comments

Comments
 (0)