From 9ea57f19beb17c0c1e6ba78cbcf61709f3d59693 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 22 Oct 2022 05:41:28 +0100 Subject: [PATCH 1/4] add wrapping/checked/saturating assist --- .../handlers/replace_arith_with_checked.rs | 33 +++++ .../handlers/replace_arith_with_saturating.rs | 33 +++++ .../handlers/replace_arith_with_wrapping.rs | 33 +++++ crates/ide-assists/src/lib.rs | 7 ++ crates/ide-assists/src/utils.rs | 113 +++++++++++++++++- 5 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 crates/ide-assists/src/handlers/replace_arith_with_checked.rs create mode 100644 crates/ide-assists/src/handlers/replace_arith_with_saturating.rs create mode 100644 crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs diff --git a/crates/ide-assists/src/handlers/replace_arith_with_checked.rs b/crates/ide-assists/src/handlers/replace_arith_with_checked.rs new file mode 100644 index 000000000000..419b6febf1c1 --- /dev/null +++ b/crates/ide-assists/src/handlers/replace_arith_with_checked.rs @@ -0,0 +1,33 @@ +use crate::assist_context::{AssistContext, Assists}; +use crate::utils::{replace_arith, ArithKind}; + +pub(crate) fn replace_arith_with_checked( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Checked) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn replace_arith_with_saturating_add() { + check_assist( + replace_arith_with_checked, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.checked_add(2); +} +"#, + ) + } +} diff --git a/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs b/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs new file mode 100644 index 000000000000..8c620ad93f02 --- /dev/null +++ b/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs @@ -0,0 +1,33 @@ +use crate::assist_context::{AssistContext, Assists}; +use crate::utils::{replace_arith, ArithKind}; + +pub(crate) fn replace_arith_with_saturating( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Saturating) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn replace_arith_with_saturating_add() { + check_assist( + replace_arith_with_saturating, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.saturating_add(2); +} +"#, + ) + } +} diff --git a/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs b/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs new file mode 100644 index 000000000000..0054f5802637 --- /dev/null +++ b/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs @@ -0,0 +1,33 @@ +use crate::assist_context::{AssistContext, Assists}; +use crate::utils::{replace_arith, ArithKind}; + +pub(crate) fn replace_arith_with_wrapping( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Wrapping) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn replace_arith_with_saturating_add() { + check_assist( + replace_arith_with_wrapping, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.wrapping_add(2); +} +"#, + ) + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index a07318cefad2..5728730c9130 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -183,6 +183,10 @@ mod handlers { mod replace_derive_with_manual_impl; mod replace_if_let_with_match; mod replace_or_with_or_else; + mod replace_arith_with_saturating; + mod replace_arith_with_checked; + mod replace_arith_with_wrapping; + mod replace_arith_with_saturating; mod introduce_named_generic; mod replace_let_with_if_let; mod replace_qualified_name_with_use; @@ -286,6 +290,9 @@ mod handlers { replace_or_with_or_else::replace_or_with_or_else, replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_qualified_name_with_use::replace_qualified_name_with_use, + replace_arith_with_wrapping::replace_arith_with_wrapping, + replace_arith_with_checked::replace_arith_with_checked, + replace_arith_with_saturating::replace_arith_with_saturating, sort_items::sort_items, split_import::split_import, toggle_ignore::toggle_ignore, diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index db32e7182c44..dd2b6785c4b1 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -4,21 +4,27 @@ use std::ops; pub(crate) use gen_trait_fn_body::gen_trait_fn_body; use hir::{db::HirDatabase, HirDisplay, Semantics}; -use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap}; +use ide_db::{ + assists::{AssistId, AssistKind}, + famous_defs::FamousDefs, + path_transform::PathTransform, + RootDatabase, SnippetCap, +}; use stdx::format_to; use syntax::{ ast::{ self, edit::{self, AstNodeEdit}, edit_in_place::{AttrsOwnerEdit, Removable}, - make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, + make, ArithOp, BinExpr, BinaryOp, Expr, HasArgList, HasAttrs, HasGenericParams, HasName, + HasTypeBounds, Whitespace, }, - ted, AstNode, AstToken, Direction, SourceFile, + ted, AstNode, AstToken, Direction, SmolStr, SourceFile, SyntaxKind::*, SyntaxNode, TextRange, TextSize, T, }; -use crate::assist_context::{AssistContext, SourceChangeBuilder}; +use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder}; pub(crate) mod suggest_name; mod gen_trait_fn_body; @@ -705,3 +711,102 @@ pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgLi } make::arg_list(args) } + +pub(crate) enum ArithKind { + Saturating, + Wrapping, + Checked, +} + +impl ArithKind { + fn assist_id(&self) -> AssistId { + let s = match self { + ArithKind::Saturating => "replace_arith_with_saturating", + ArithKind::Checked => "replace_arith_with_saturating", + ArithKind::Wrapping => "replace_arith_with_saturating", + }; + + AssistId(s, AssistKind::RefactorRewrite) + } + + fn label(&self) -> &'static str { + match self { + ArithKind::Saturating => "Replace arithmetic with call to saturating_*", + ArithKind::Checked => "Replace arithmetic with call to checked_*", + ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*", + } + } + + fn method_name(&self, op: ArithOp) -> SmolStr { + // is this too much effort to avoid an allocation? is there a better way? + let mut bytes = [0u8; 14]; + let prefix = match self { + ArithKind::Checked => "checked_", + ArithKind::Wrapping => "wrapping_", + ArithKind::Saturating => "saturating_", + }; + + bytes[0..(prefix.len())].copy_from_slice(prefix.as_bytes()); + + let suffix = match op { + ArithOp::Add => "add", + ArithOp::Sub => "sub", + ArithOp::Mul => "mul", + ArithOp::Div => "div", + _ => unreachable!("this function should only be called with +, -, / or *"), + }; + + bytes[(prefix.len())..(prefix.len() + suffix.len())].copy_from_slice(suffix.as_bytes()); + + let len = prefix.len() + suffix.len(); + let s = core::str::from_utf8(&bytes[0..len]).unwrap(); + SmolStr::from(s) + } +} + +pub(crate) fn replace_arith( + acc: &mut Assists, + ctx: &AssistContext<'_>, + kind: ArithKind, +) -> Option<()> { + let (lhs, op, rhs) = parse_binary_op(ctx)?; + + let start = lhs.syntax().text_range().start(); + let end = rhs.syntax().text_range().end(); + let range = TextRange::new(start, end); + + acc.add(kind.assist_id(), kind.label(), range, |builder| { + let method_name = kind.method_name(op); + + builder.replace(range, format!("{lhs}.{method_name}({rhs})")) + }) +} + +/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`) +fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(Expr, ArithOp, Expr)> { + let expr = ctx.find_node_at_offset::()?; + + let op = match expr.op_kind() { + Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add, + Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub, + Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul, + Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div, + _ => return None, + }; + + let lhs = expr.lhs()?; + let rhs = expr.rhs()?; + + Some((lhs, op, rhs)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn arith_kind_method_name() { + assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add"); + assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub"); + } +} From 5b56d7bd90fe4f11b67005bf9c6044ea03251dd3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 22 Oct 2022 05:43:57 +0100 Subject: [PATCH 2/4] remove duplicate mod definition --- crates/ide-assists/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 5728730c9130..3938fa098353 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -183,7 +183,6 @@ mod handlers { mod replace_derive_with_manual_impl; mod replace_if_let_with_match; mod replace_or_with_or_else; - mod replace_arith_with_saturating; mod replace_arith_with_checked; mod replace_arith_with_wrapping; mod replace_arith_with_saturating; From 3dd2f99c49d225b08e38ccd1981e1adef8fd2901 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 22 Oct 2022 06:23:23 +0100 Subject: [PATCH 3/4] fix tests --- crates/hir/src/lib.rs | 7 +++ .../handlers/replace_arith_with_checked.rs | 20 ++++++-- .../handlers/replace_arith_with_saturating.rs | 15 ++++++ .../handlers/replace_arith_with_wrapping.rs | 15 ++++++ crates/ide-assists/src/tests/generated.rs | 51 +++++++++++++++++++ crates/ide-assists/src/utils.rs | 15 +++++- 6 files changed, 117 insertions(+), 6 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index f5324208c9a4..1e927b898ad6 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2857,6 +2857,13 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Uint(UintTy::Usize))) } + pub fn is_int_or_uint(&self) -> bool { + match self.ty.kind(Interner) { + TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)) => true, + _ => false, + } + } + pub fn remove_ref(&self) -> Option { match &self.ty.kind(Interner) { TyKind::Ref(.., ty) => Some(self.derived(ty.clone())), diff --git a/crates/ide-assists/src/handlers/replace_arith_with_checked.rs b/crates/ide-assists/src/handlers/replace_arith_with_checked.rs index 419b6febf1c1..ff1fba5818cc 100644 --- a/crates/ide-assists/src/handlers/replace_arith_with_checked.rs +++ b/crates/ide-assists/src/handlers/replace_arith_with_checked.rs @@ -1,10 +1,22 @@ use crate::assist_context::{AssistContext, Assists}; use crate::utils::{replace_arith, ArithKind}; -pub(crate) fn replace_arith_with_checked( - acc: &mut Assists, - ctx: &AssistContext<'_>, -) -> Option<()> { +// Assist: replace_arith_with_checked +// +// Replaces arithmetic on integers with the `checked_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.checked_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_checked(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { replace_arith(acc, ctx, ArithKind::Checked) } diff --git a/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs b/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs index 8c620ad93f02..717875e84f03 100644 --- a/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs +++ b/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs @@ -1,6 +1,21 @@ use crate::assist_context::{AssistContext, Assists}; use crate::utils::{replace_arith, ArithKind}; +// Assist: replace_arith_with_saturating +// +// Replaces arithmetic on integers with the `saturating_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.saturating_add(2); +// } +// ``` pub(crate) fn replace_arith_with_saturating( acc: &mut Assists, ctx: &AssistContext<'_>, diff --git a/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs b/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs index 0054f5802637..e0a90292b194 100644 --- a/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs +++ b/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs @@ -1,6 +1,21 @@ use crate::assist_context::{AssistContext, Assists}; use crate::utils::{replace_arith, ArithKind}; +// Assist: replace_arith_with_wrapping +// +// Replaces arithmetic on integers with the `wrapping_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.wrapping_add(2); +// } +// ``` pub(crate) fn replace_arith_with_wrapping( acc: &mut Assists, ctx: &AssistContext<'_>, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 2c4000efe0fa..7eac40d6e8f0 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1978,6 +1978,57 @@ impl Foo for Bar { ) } +#[test] +fn doctest_replace_arith_with_checked() { + check_doc_test( + "replace_arith_with_checked", + r#####" +fn main() { + let x = 1 $0+ 2; +} +"#####, + r#####" +fn main() { + let x = 1.checked_add(2); +} +"#####, + ) +} + +#[test] +fn doctest_replace_arith_with_saturating() { + check_doc_test( + "replace_arith_with_saturating", + r#####" +fn main() { + let x = 1 $0+ 2; +} +"#####, + r#####" +fn main() { + let x = 1.saturating_add(2); +} +"#####, + ) +} + +#[test] +fn doctest_replace_arith_with_wrapping() { + check_doc_test( + "replace_arith_with_wrapping", + r#####" +fn main() { + let x = 1 $0+ 2; +} +"#####, + r#####" +fn main() { + let x = 1.wrapping_add(2); +} +"#####, + ) +} + #[test] fn doctest_replace_char_with_string() { check_doc_test( diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index dd2b6785c4b1..0227222c0896 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -722,8 +722,8 @@ impl ArithKind { fn assist_id(&self) -> AssistId { let s = match self { ArithKind::Saturating => "replace_arith_with_saturating", - ArithKind::Checked => "replace_arith_with_saturating", - ArithKind::Wrapping => "replace_arith_with_saturating", + ArithKind::Checked => "replace_arith_with_checked", + ArithKind::Wrapping => "replace_arith_with_wrapping", }; AssistId(s, AssistKind::RefactorRewrite) @@ -771,6 +771,10 @@ pub(crate) fn replace_arith( ) -> Option<()> { let (lhs, op, rhs) = parse_binary_op(ctx)?; + if !is_primitive_int(ctx, &lhs) || !is_primitive_int(ctx, &rhs) { + return None; + } + let start = lhs.syntax().text_range().start(); let end = rhs.syntax().text_range().end(); let range = TextRange::new(start, end); @@ -782,6 +786,13 @@ pub(crate) fn replace_arith( }) } +fn is_primitive_int(ctx: &AssistContext<'_>, expr: &Expr) -> bool { + match ctx.sema.type_of_expr(expr) { + Some(ty) => ty.adjusted().is_int_or_uint(), + _ => false, + } +} + /// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`) fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(Expr, ArithOp, Expr)> { let expr = ctx.find_node_at_offset::()?; From 0dd26821783ccc809fb0995f178fd6c53f6eebd7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 9 Jan 2023 11:59:09 +0100 Subject: [PATCH 4/4] Refactor replace_arith assists into one module --- .../src/handlers/replace_arith_op.rs | 226 ++++++++++++++++++ .../handlers/replace_arith_with_checked.rs | 45 ---- .../handlers/replace_arith_with_saturating.rs | 48 ---- .../handlers/replace_arith_with_wrapping.rs | 48 ---- crates/ide-assists/src/lib.rs | 10 +- crates/ide-assists/src/utils.rs | 124 +--------- 6 files changed, 234 insertions(+), 267 deletions(-) create mode 100644 crates/ide-assists/src/handlers/replace_arith_op.rs delete mode 100644 crates/ide-assists/src/handlers/replace_arith_with_checked.rs delete mode 100644 crates/ide-assists/src/handlers/replace_arith_with_saturating.rs delete mode 100644 crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs diff --git a/crates/ide-assists/src/handlers/replace_arith_op.rs b/crates/ide-assists/src/handlers/replace_arith_op.rs new file mode 100644 index 000000000000..f1ca35cafc3a --- /dev/null +++ b/crates/ide-assists/src/handlers/replace_arith_op.rs @@ -0,0 +1,226 @@ +use ide_db::assists::{AssistId, AssistKind, GroupLabel}; +use syntax::{ + ast::{self, ArithOp, BinaryOp}, + AstNode, TextRange, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: replace_arith_with_checked +// +// Replaces arithmetic on integers with the `checked_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.checked_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_checked(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Checked) +} + +// Assist: replace_arith_with_saturating +// +// Replaces arithmetic on integers with the `saturating_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.saturating_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_saturating( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Saturating) +} + +// Assist: replace_arith_with_wrapping +// +// Replaces arithmetic on integers with the `wrapping_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.wrapping_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_wrapping( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Wrapping) +} + +fn replace_arith(acc: &mut Assists, ctx: &AssistContext<'_>, kind: ArithKind) -> Option<()> { + let (lhs, op, rhs) = parse_binary_op(ctx)?; + + if !is_primitive_int(ctx, &lhs) || !is_primitive_int(ctx, &rhs) { + return None; + } + + let start = lhs.syntax().text_range().start(); + let end = rhs.syntax().text_range().end(); + let range = TextRange::new(start, end); + + acc.add_group( + &GroupLabel("replace_arith".into()), + kind.assist_id(), + kind.label(), + range, + |builder| { + let method_name = kind.method_name(op); + + builder.replace(range, format!("{lhs}.{method_name}({rhs})")) + }, + ) +} + +fn is_primitive_int(ctx: &AssistContext<'_>, expr: &ast::Expr) -> bool { + match ctx.sema.type_of_expr(expr) { + Some(ty) => ty.adjusted().is_int_or_uint(), + _ => false, + } +} + +/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`) +fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(ast::Expr, ArithOp, ast::Expr)> { + let expr = ctx.find_node_at_offset::()?; + + let op = match expr.op_kind() { + Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add, + Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub, + Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul, + Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div, + _ => return None, + }; + + let lhs = expr.lhs()?; + let rhs = expr.rhs()?; + + Some((lhs, op, rhs)) +} + +pub(crate) enum ArithKind { + Saturating, + Wrapping, + Checked, +} + +impl ArithKind { + fn assist_id(&self) -> AssistId { + let s = match self { + ArithKind::Saturating => "replace_arith_with_saturating", + ArithKind::Checked => "replace_arith_with_checked", + ArithKind::Wrapping => "replace_arith_with_wrapping", + }; + + AssistId(s, AssistKind::RefactorRewrite) + } + + fn label(&self) -> &'static str { + match self { + ArithKind::Saturating => "Replace arithmetic with call to saturating_*", + ArithKind::Checked => "Replace arithmetic with call to checked_*", + ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*", + } + } + + fn method_name(&self, op: ArithOp) -> String { + let prefix = match self { + ArithKind::Checked => "checked_", + ArithKind::Wrapping => "wrapping_", + ArithKind::Saturating => "saturating_", + }; + + let suffix = match op { + ArithOp::Add => "add", + ArithOp::Sub => "sub", + ArithOp::Mul => "mul", + ArithOp::Div => "div", + _ => unreachable!("this function should only be called with +, -, / or *"), + }; + format!("{prefix}{suffix}") + } +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn arith_kind_method_name() { + assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add"); + assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub"); + } + + #[test] + fn replace_arith_with_checked_add() { + check_assist( + replace_arith_with_checked, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.checked_add(2); +} +"#, + ) + } + + #[test] + fn replace_arith_with_saturating_add() { + check_assist( + replace_arith_with_saturating, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.saturating_add(2); +} +"#, + ) + } + + #[test] + fn replace_arith_with_wrapping_add() { + check_assist( + replace_arith_with_wrapping, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.wrapping_add(2); +} +"#, + ) + } +} diff --git a/crates/ide-assists/src/handlers/replace_arith_with_checked.rs b/crates/ide-assists/src/handlers/replace_arith_with_checked.rs deleted file mode 100644 index ff1fba5818cc..000000000000 --- a/crates/ide-assists/src/handlers/replace_arith_with_checked.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::assist_context::{AssistContext, Assists}; -use crate::utils::{replace_arith, ArithKind}; - -// Assist: replace_arith_with_checked -// -// Replaces arithmetic on integers with the `checked_*` equivalent. -// -// ``` -// fn main() { -// let x = 1 $0+ 2; -// } -// ``` -// -> -// ``` -// fn main() { -// let x = 1.checked_add(2); -// } -// ``` -pub(crate) fn replace_arith_with_checked(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - replace_arith(acc, ctx, ArithKind::Checked) -} - -#[cfg(test)] -mod tests { - use crate::tests::check_assist; - - use super::*; - - #[test] - fn replace_arith_with_saturating_add() { - check_assist( - replace_arith_with_checked, - r#" -fn main() { - let x = 1 $0+ 2; -} -"#, - r#" -fn main() { - let x = 1.checked_add(2); -} -"#, - ) - } -} diff --git a/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs b/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs deleted file mode 100644 index 717875e84f03..000000000000 --- a/crates/ide-assists/src/handlers/replace_arith_with_saturating.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::assist_context::{AssistContext, Assists}; -use crate::utils::{replace_arith, ArithKind}; - -// Assist: replace_arith_with_saturating -// -// Replaces arithmetic on integers with the `saturating_*` equivalent. -// -// ``` -// fn main() { -// let x = 1 $0+ 2; -// } -// ``` -// -> -// ``` -// fn main() { -// let x = 1.saturating_add(2); -// } -// ``` -pub(crate) fn replace_arith_with_saturating( - acc: &mut Assists, - ctx: &AssistContext<'_>, -) -> Option<()> { - replace_arith(acc, ctx, ArithKind::Saturating) -} - -#[cfg(test)] -mod tests { - use crate::tests::check_assist; - - use super::*; - - #[test] - fn replace_arith_with_saturating_add() { - check_assist( - replace_arith_with_saturating, - r#" -fn main() { - let x = 1 $0+ 2; -} -"#, - r#" -fn main() { - let x = 1.saturating_add(2); -} -"#, - ) - } -} diff --git a/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs b/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs deleted file mode 100644 index e0a90292b194..000000000000 --- a/crates/ide-assists/src/handlers/replace_arith_with_wrapping.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::assist_context::{AssistContext, Assists}; -use crate::utils::{replace_arith, ArithKind}; - -// Assist: replace_arith_with_wrapping -// -// Replaces arithmetic on integers with the `wrapping_*` equivalent. -// -// ``` -// fn main() { -// let x = 1 $0+ 2; -// } -// ``` -// -> -// ``` -// fn main() { -// let x = 1.wrapping_add(2); -// } -// ``` -pub(crate) fn replace_arith_with_wrapping( - acc: &mut Assists, - ctx: &AssistContext<'_>, -) -> Option<()> { - replace_arith(acc, ctx, ArithKind::Wrapping) -} - -#[cfg(test)] -mod tests { - use crate::tests::check_assist; - - use super::*; - - #[test] - fn replace_arith_with_saturating_add() { - check_assist( - replace_arith_with_wrapping, - r#" -fn main() { - let x = 1 $0+ 2; -} -"#, - r#" -fn main() { - let x = 1.wrapping_add(2); -} -"#, - ) - } -} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 3938fa098353..0255ffdea3f2 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -183,9 +183,7 @@ mod handlers { mod replace_derive_with_manual_impl; mod replace_if_let_with_match; mod replace_or_with_or_else; - mod replace_arith_with_checked; - mod replace_arith_with_wrapping; - mod replace_arith_with_saturating; + mod replace_arith_op; mod introduce_named_generic; mod replace_let_with_if_let; mod replace_qualified_name_with_use; @@ -289,9 +287,9 @@ mod handlers { replace_or_with_or_else::replace_or_with_or_else, replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_qualified_name_with_use::replace_qualified_name_with_use, - replace_arith_with_wrapping::replace_arith_with_wrapping, - replace_arith_with_checked::replace_arith_with_checked, - replace_arith_with_saturating::replace_arith_with_saturating, + replace_arith_op::replace_arith_with_wrapping, + replace_arith_op::replace_arith_with_checked, + replace_arith_op::replace_arith_with_saturating, sort_items::sort_items, split_import::split_import, toggle_ignore::toggle_ignore, diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 0227222c0896..db32e7182c44 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -4,27 +4,21 @@ use std::ops; pub(crate) use gen_trait_fn_body::gen_trait_fn_body; use hir::{db::HirDatabase, HirDisplay, Semantics}; -use ide_db::{ - assists::{AssistId, AssistKind}, - famous_defs::FamousDefs, - path_transform::PathTransform, - RootDatabase, SnippetCap, -}; +use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap}; use stdx::format_to; use syntax::{ ast::{ self, edit::{self, AstNodeEdit}, edit_in_place::{AttrsOwnerEdit, Removable}, - make, ArithOp, BinExpr, BinaryOp, Expr, HasArgList, HasAttrs, HasGenericParams, HasName, - HasTypeBounds, Whitespace, + make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, }, - ted, AstNode, AstToken, Direction, SmolStr, SourceFile, + ted, AstNode, AstToken, Direction, SourceFile, SyntaxKind::*, SyntaxNode, TextRange, TextSize, T, }; -use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder}; +use crate::assist_context::{AssistContext, SourceChangeBuilder}; pub(crate) mod suggest_name; mod gen_trait_fn_body; @@ -711,113 +705,3 @@ pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgLi } make::arg_list(args) } - -pub(crate) enum ArithKind { - Saturating, - Wrapping, - Checked, -} - -impl ArithKind { - fn assist_id(&self) -> AssistId { - let s = match self { - ArithKind::Saturating => "replace_arith_with_saturating", - ArithKind::Checked => "replace_arith_with_checked", - ArithKind::Wrapping => "replace_arith_with_wrapping", - }; - - AssistId(s, AssistKind::RefactorRewrite) - } - - fn label(&self) -> &'static str { - match self { - ArithKind::Saturating => "Replace arithmetic with call to saturating_*", - ArithKind::Checked => "Replace arithmetic with call to checked_*", - ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*", - } - } - - fn method_name(&self, op: ArithOp) -> SmolStr { - // is this too much effort to avoid an allocation? is there a better way? - let mut bytes = [0u8; 14]; - let prefix = match self { - ArithKind::Checked => "checked_", - ArithKind::Wrapping => "wrapping_", - ArithKind::Saturating => "saturating_", - }; - - bytes[0..(prefix.len())].copy_from_slice(prefix.as_bytes()); - - let suffix = match op { - ArithOp::Add => "add", - ArithOp::Sub => "sub", - ArithOp::Mul => "mul", - ArithOp::Div => "div", - _ => unreachable!("this function should only be called with +, -, / or *"), - }; - - bytes[(prefix.len())..(prefix.len() + suffix.len())].copy_from_slice(suffix.as_bytes()); - - let len = prefix.len() + suffix.len(); - let s = core::str::from_utf8(&bytes[0..len]).unwrap(); - SmolStr::from(s) - } -} - -pub(crate) fn replace_arith( - acc: &mut Assists, - ctx: &AssistContext<'_>, - kind: ArithKind, -) -> Option<()> { - let (lhs, op, rhs) = parse_binary_op(ctx)?; - - if !is_primitive_int(ctx, &lhs) || !is_primitive_int(ctx, &rhs) { - return None; - } - - let start = lhs.syntax().text_range().start(); - let end = rhs.syntax().text_range().end(); - let range = TextRange::new(start, end); - - acc.add(kind.assist_id(), kind.label(), range, |builder| { - let method_name = kind.method_name(op); - - builder.replace(range, format!("{lhs}.{method_name}({rhs})")) - }) -} - -fn is_primitive_int(ctx: &AssistContext<'_>, expr: &Expr) -> bool { - match ctx.sema.type_of_expr(expr) { - Some(ty) => ty.adjusted().is_int_or_uint(), - _ => false, - } -} - -/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`) -fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(Expr, ArithOp, Expr)> { - let expr = ctx.find_node_at_offset::()?; - - let op = match expr.op_kind() { - Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add, - Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub, - Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul, - Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div, - _ => return None, - }; - - let lhs = expr.lhs()?; - let rhs = expr.rhs()?; - - Some((lhs, op, rhs)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn arith_kind_method_name() { - assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add"); - assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub"); - } -}