@@ -10,7 +10,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder};
1010use rustc_hir as hir;
1111use rustc_hir:: intravisit:: { walk_body, walk_expr, walk_ty, FnKind , NestedVisitorMap , Visitor } ;
1212use rustc_hir:: {
13- BinOpKind , Body , Expr , ExprKind , FnDecl , FnRetTy , FnSig , GenericArg , GenericParamKind , HirId , ImplItem ,
13+ BinOpKind , Block , Body , Expr , ExprKind , FnDecl , FnRetTy , FnSig , GenericArg , GenericParamKind , HirId , ImplItem ,
1414 ImplItemKind , Item , ItemKind , Lifetime , Local , MatchSource , MutTy , Mutability , QPath , Stmt , StmtKind , TraitFn ,
1515 TraitItem , TraitItemKind , TyKind , UnOp ,
1616} ;
@@ -29,10 +29,10 @@ use rustc_typeck::hir_ty_to_ty;
2929use crate :: consts:: { constant, Constant } ;
3030use crate :: utils:: paths;
3131use crate :: utils:: {
32- clip, comparisons, differing_macro_contexts, higher, in_constant, int_bits, is_type_diagnostic_item,
32+ clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of , int_bits, is_type_diagnostic_item,
3333 last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal:: NumericLiteral ,
34- qpath_res, same_tys, sext, snippet, snippet_opt , snippet_with_applicability , snippet_with_macro_callsite ,
35- span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
34+ qpath_res, same_tys, sext, snippet, snippet_block_with_applicability , snippet_opt , snippet_with_applicability ,
35+ snippet_with_macro_callsite , span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
3636} ;
3737
3838declare_clippy_lint ! {
@@ -779,31 +779,124 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg {
779779
780780 match expr. kind {
781781 ExprKind :: Call ( _, args) | ExprKind :: MethodCall ( _, _, args) => {
782- for arg in args {
783- if is_unit ( cx. tables . expr_ty ( arg) ) && !is_unit_literal ( arg) {
784- if let ExprKind :: Match ( .., match_source) = & arg. kind {
785- if * match_source == MatchSource :: TryDesugar {
786- continue ;
782+ let args_to_recover = args
783+ . iter ( )
784+ . filter ( |arg| {
785+ if is_unit ( cx. tables . expr_ty ( arg) ) && !is_unit_literal ( arg) {
786+ if let ExprKind :: Match ( .., MatchSource :: TryDesugar ) = & arg. kind {
787+ false
788+ } else {
789+ true
787790 }
791+ } else {
792+ false
788793 }
789-
790- span_lint_and_sugg (
791- cx,
792- UNIT_ARG ,
793- arg. span ,
794- "passing a unit value to a function" ,
795- "if you intended to pass a unit value, use a unit literal instead" ,
796- "()" . to_string ( ) ,
797- Applicability :: MaybeIncorrect ,
798- ) ;
799- }
794+ } )
795+ . collect :: < Vec < _ > > ( ) ;
796+ if !args_to_recover. is_empty ( ) {
797+ lint_unit_args ( cx, expr, & args_to_recover) ;
800798 }
801799 } ,
802800 _ => ( ) ,
803801 }
804802 }
805803}
806804
805+ fn lint_unit_args ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > , args_to_recover : & [ & Expr < ' _ > ] ) {
806+ let mut applicability = Applicability :: MachineApplicable ;
807+ let ( singular, plural) = if args_to_recover. len ( ) > 1 {
808+ ( "" , "s" )
809+ } else {
810+ ( "a " , "" )
811+ } ;
812+ span_lint_and_then (
813+ cx,
814+ UNIT_ARG ,
815+ expr. span ,
816+ & format ! ( "passing {}unit value{} to a function" , singular, plural) ,
817+ |db| {
818+ let mut or = "" ;
819+ args_to_recover
820+ . iter ( )
821+ . filter_map ( |arg| {
822+ if_chain ! {
823+ if let ExprKind :: Block ( block, _) = arg. kind;
824+ if block. expr. is_none( ) ;
825+ if let Some ( last_stmt) = block. stmts. iter( ) . last( ) ;
826+ if let StmtKind :: Semi ( last_expr) = last_stmt. kind;
827+ if let Some ( snip) = snippet_opt( cx, last_expr. span) ;
828+ then {
829+ Some ( (
830+ last_stmt. span,
831+ snip,
832+ ) )
833+ }
834+ else {
835+ None
836+ }
837+ }
838+ } )
839+ . for_each ( |( span, sugg) | {
840+ db. span_suggestion (
841+ span,
842+ "remove the semicolon from the last statement in the block" ,
843+ sugg,
844+ Applicability :: MaybeIncorrect ,
845+ ) ;
846+ or = "or " ;
847+ } ) ;
848+ let sugg = args_to_recover
849+ . iter ( )
850+ . filter ( |arg| !is_empty_block ( arg) )
851+ . enumerate ( )
852+ . map ( |( i, arg) | {
853+ let indent = if i == 0 {
854+ 0
855+ } else {
856+ indent_of ( cx, expr. span ) . unwrap_or ( 0 )
857+ } ;
858+ format ! (
859+ "{}{};" ,
860+ " " . repeat( indent) ,
861+ snippet_block_with_applicability( cx, arg. span, ".." , Some ( expr. span) , & mut applicability)
862+ )
863+ } )
864+ . collect :: < Vec < String > > ( ) ;
865+ let mut and = "" ;
866+ if !sugg. is_empty ( ) {
867+ let plural = if sugg. len ( ) > 1 { "s" } else { "" } ;
868+ db. span_suggestion (
869+ expr. span . with_hi ( expr. span . lo ( ) ) ,
870+ & format ! ( "{}move the expression{} in front of the call..." , or, plural) ,
871+ format ! ( "{}\n " , sugg. join( "\n " ) ) ,
872+ applicability,
873+ ) ;
874+ and = "...and "
875+ }
876+ db. multipart_suggestion (
877+ & format ! ( "{}use {}unit literal{} instead" , and, singular, plural) ,
878+ args_to_recover
879+ . iter ( )
880+ . map ( |arg| ( arg. span , "()" . to_string ( ) ) )
881+ . collect :: < Vec < _ > > ( ) ,
882+ applicability,
883+ ) ;
884+ } ,
885+ ) ;
886+ }
887+
888+ fn is_empty_block ( expr : & Expr < ' _ > ) -> bool {
889+ matches ! (
890+ expr. kind,
891+ ExprKind :: Block (
892+ Block {
893+ stmts: & [ ] , expr: None , ..
894+ } ,
895+ _,
896+ )
897+ )
898+ }
899+
807900fn is_questionmark_desugar_marked_call ( expr : & Expr < ' _ > ) -> bool {
808901 use rustc_span:: hygiene:: DesugaringKind ;
809902 if let ExprKind :: Call ( ref callee, _) = expr. kind {
0 commit comments