@@ -4,21 +4,27 @@ use std::ops;
44
55pub ( crate ) use gen_trait_fn_body:: gen_trait_fn_body;
66use 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+ } ;
813use stdx:: format_to;
914use 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
2329pub ( crate ) mod suggest_name;
2430mod 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