Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 9ea57f1

Browse files
committed
add wrapping/checked/saturating assist
1 parent 69f01fd commit 9ea57f1

File tree

5 files changed

+215
-4
lines changed

5 files changed

+215
-4
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::assist_context::{AssistContext, Assists};
2+
use crate::utils::{replace_arith, ArithKind};
3+
4+
pub(crate) fn replace_arith_with_checked(
5+
acc: &mut Assists,
6+
ctx: &AssistContext<'_>,
7+
) -> Option<()> {
8+
replace_arith(acc, ctx, ArithKind::Checked)
9+
}
10+
11+
#[cfg(test)]
12+
mod tests {
13+
use crate::tests::check_assist;
14+
15+
use super::*;
16+
17+
#[test]
18+
fn replace_arith_with_saturating_add() {
19+
check_assist(
20+
replace_arith_with_checked,
21+
r#"
22+
fn main() {
23+
let x = 1 $0+ 2;
24+
}
25+
"#,
26+
r#"
27+
fn main() {
28+
let x = 1.checked_add(2);
29+
}
30+
"#,
31+
)
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::assist_context::{AssistContext, Assists};
2+
use crate::utils::{replace_arith, ArithKind};
3+
4+
pub(crate) fn replace_arith_with_saturating(
5+
acc: &mut Assists,
6+
ctx: &AssistContext<'_>,
7+
) -> Option<()> {
8+
replace_arith(acc, ctx, ArithKind::Saturating)
9+
}
10+
11+
#[cfg(test)]
12+
mod tests {
13+
use crate::tests::check_assist;
14+
15+
use super::*;
16+
17+
#[test]
18+
fn replace_arith_with_saturating_add() {
19+
check_assist(
20+
replace_arith_with_saturating,
21+
r#"
22+
fn main() {
23+
let x = 1 $0+ 2;
24+
}
25+
"#,
26+
r#"
27+
fn main() {
28+
let x = 1.saturating_add(2);
29+
}
30+
"#,
31+
)
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::assist_context::{AssistContext, Assists};
2+
use crate::utils::{replace_arith, ArithKind};
3+
4+
pub(crate) fn replace_arith_with_wrapping(
5+
acc: &mut Assists,
6+
ctx: &AssistContext<'_>,
7+
) -> Option<()> {
8+
replace_arith(acc, ctx, ArithKind::Wrapping)
9+
}
10+
11+
#[cfg(test)]
12+
mod tests {
13+
use crate::tests::check_assist;
14+
15+
use super::*;
16+
17+
#[test]
18+
fn replace_arith_with_saturating_add() {
19+
check_assist(
20+
replace_arith_with_wrapping,
21+
r#"
22+
fn main() {
23+
let x = 1 $0+ 2;
24+
}
25+
"#,
26+
r#"
27+
fn main() {
28+
let x = 1.wrapping_add(2);
29+
}
30+
"#,
31+
)
32+
}
33+
}

crates/ide-assists/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ mod handlers {
183183
mod replace_derive_with_manual_impl;
184184
mod replace_if_let_with_match;
185185
mod replace_or_with_or_else;
186+
mod replace_arith_with_saturating;
187+
mod replace_arith_with_checked;
188+
mod replace_arith_with_wrapping;
189+
mod replace_arith_with_saturating;
186190
mod introduce_named_generic;
187191
mod replace_let_with_if_let;
188192
mod replace_qualified_name_with_use;
@@ -286,6 +290,9 @@ mod handlers {
286290
replace_or_with_or_else::replace_or_with_or_else,
287291
replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
288292
replace_qualified_name_with_use::replace_qualified_name_with_use,
293+
replace_arith_with_wrapping::replace_arith_with_wrapping,
294+
replace_arith_with_checked::replace_arith_with_checked,
295+
replace_arith_with_saturating::replace_arith_with_saturating,
289296
sort_items::sort_items,
290297
split_import::split_import,
291298
toggle_ignore::toggle_ignore,

crates/ide-assists/src/utils.rs

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,27 @@ use std::ops;
44

55
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
66
use hir::{db::HirDatabase, HirDisplay, Semantics};
7-
use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap};
7+
use ide_db::{
8+
assists::{AssistId, AssistKind},
9+
famous_defs::FamousDefs,
10+
path_transform::PathTransform,
11+
RootDatabase, SnippetCap,
12+
};
813
use stdx::format_to;
914
use syntax::{
1015
ast::{
1116
self,
1217
edit::{self, AstNodeEdit},
1318
edit_in_place::{AttrsOwnerEdit, Removable},
14-
make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
19+
make, ArithOp, BinExpr, BinaryOp, Expr, HasArgList, HasAttrs, HasGenericParams, HasName,
20+
HasTypeBounds, Whitespace,
1521
},
16-
ted, AstNode, AstToken, Direction, SourceFile,
22+
ted, AstNode, AstToken, Direction, SmolStr, SourceFile,
1723
SyntaxKind::*,
1824
SyntaxNode, TextRange, TextSize, T,
1925
};
2026

21-
use crate::assist_context::{AssistContext, SourceChangeBuilder};
27+
use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
2228

2329
pub(crate) mod suggest_name;
2430
mod gen_trait_fn_body;
@@ -705,3 +711,102 @@ pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgLi
705711
}
706712
make::arg_list(args)
707713
}
714+
715+
pub(crate) enum ArithKind {
716+
Saturating,
717+
Wrapping,
718+
Checked,
719+
}
720+
721+
impl ArithKind {
722+
fn assist_id(&self) -> AssistId {
723+
let s = match self {
724+
ArithKind::Saturating => "replace_arith_with_saturating",
725+
ArithKind::Checked => "replace_arith_with_saturating",
726+
ArithKind::Wrapping => "replace_arith_with_saturating",
727+
};
728+
729+
AssistId(s, AssistKind::RefactorRewrite)
730+
}
731+
732+
fn label(&self) -> &'static str {
733+
match self {
734+
ArithKind::Saturating => "Replace arithmetic with call to saturating_*",
735+
ArithKind::Checked => "Replace arithmetic with call to checked_*",
736+
ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*",
737+
}
738+
}
739+
740+
fn method_name(&self, op: ArithOp) -> SmolStr {
741+
// is this too much effort to avoid an allocation? is there a better way?
742+
let mut bytes = [0u8; 14];
743+
let prefix = match self {
744+
ArithKind::Checked => "checked_",
745+
ArithKind::Wrapping => "wrapping_",
746+
ArithKind::Saturating => "saturating_",
747+
};
748+
749+
bytes[0..(prefix.len())].copy_from_slice(prefix.as_bytes());
750+
751+
let suffix = match op {
752+
ArithOp::Add => "add",
753+
ArithOp::Sub => "sub",
754+
ArithOp::Mul => "mul",
755+
ArithOp::Div => "div",
756+
_ => unreachable!("this function should only be called with +, -, / or *"),
757+
};
758+
759+
bytes[(prefix.len())..(prefix.len() + suffix.len())].copy_from_slice(suffix.as_bytes());
760+
761+
let len = prefix.len() + suffix.len();
762+
let s = core::str::from_utf8(&bytes[0..len]).unwrap();
763+
SmolStr::from(s)
764+
}
765+
}
766+
767+
pub(crate) fn replace_arith(
768+
acc: &mut Assists,
769+
ctx: &AssistContext<'_>,
770+
kind: ArithKind,
771+
) -> Option<()> {
772+
let (lhs, op, rhs) = parse_binary_op(ctx)?;
773+
774+
let start = lhs.syntax().text_range().start();
775+
let end = rhs.syntax().text_range().end();
776+
let range = TextRange::new(start, end);
777+
778+
acc.add(kind.assist_id(), kind.label(), range, |builder| {
779+
let method_name = kind.method_name(op);
780+
781+
builder.replace(range, format!("{lhs}.{method_name}({rhs})"))
782+
})
783+
}
784+
785+
/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`)
786+
fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(Expr, ArithOp, Expr)> {
787+
let expr = ctx.find_node_at_offset::<BinExpr>()?;
788+
789+
let op = match expr.op_kind() {
790+
Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add,
791+
Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub,
792+
Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul,
793+
Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div,
794+
_ => return None,
795+
};
796+
797+
let lhs = expr.lhs()?;
798+
let rhs = expr.rhs()?;
799+
800+
Some((lhs, op, rhs))
801+
}
802+
803+
#[cfg(test)]
804+
mod tests {
805+
use super::*;
806+
807+
#[test]
808+
fn arith_kind_method_name() {
809+
assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add");
810+
assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub");
811+
}
812+
}

0 commit comments

Comments
 (0)