@@ -2,11 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
22use clippy_utils:: higher;
33use clippy_utils:: source:: snippet_with_applicability;
44use clippy_utils:: ty:: is_type_diagnostic_item;
5- use clippy_utils:: { eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt} ;
5+ use clippy_utils:: {
6+ eq_expr_value, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
7+ } ;
68use if_chain:: if_chain;
79use rustc_errors:: Applicability ;
810use rustc_hir:: LangItem :: { OptionNone , OptionSome , ResultErr , ResultOk } ;
9- use rustc_hir:: { BindingAnnotation , Expr , ExprKind , PatKind , QPath } ;
11+ use rustc_hir:: { BindingAnnotation , Expr , ExprKind , PatKind , PathSegment , QPath } ;
1012use rustc_lint:: { LateContext , LateLintPass } ;
1113use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1214use rustc_span:: { sym, symbol:: Symbol } ;
@@ -73,6 +75,7 @@ enum IfBlockType<'hir> {
7375fn check_is_none_or_err_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
7476 if_chain ! {
7577 if let Some ( higher:: If { cond, then, r#else } ) = higher:: If :: hir( expr) ;
78+ if !is_else_clause( cx. tcx, expr) ;
7679 if let ExprKind :: MethodCall ( segment, args, _) = & cond. kind;
7780 if let Some ( subject) = args. get( 0 ) ;
7881 let if_block = IfBlockType :: IfIs ( subject, segment. ident. name, then, r#else) ;
@@ -103,6 +106,7 @@ fn move_by_default(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
103106fn check_if_let_some_or_err_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
104107 if_chain ! {
105108 if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else } ) = higher:: IfLet :: hir( cx, expr) ;
109+ if !is_else_clause( cx. tcx, expr) ;
106110 if let PatKind :: TupleStruct ( ref path1, fields, None ) = let_pat. kind;
107111 if let PatKind :: Binding ( annot, bind_id, ident, _) = fields[ 0 ] . kind;
108112 let if_block = IfBlockType :: IfLet ( path1, ident. name, let_expr, if_then, if_else) ;
@@ -112,7 +116,12 @@ fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'
112116 let mut applicability = Applicability :: MachineApplicable ;
113117 let receiver_str = snippet_with_applicability( cx, let_expr. span, ".." , & mut applicability) ;
114118 let by_ref = matches!( annot, BindingAnnotation :: Ref | BindingAnnotation :: RefMut ) ;
115- let replacement = format!( "{}{}?" , receiver_str, if by_ref { ".as_ref()" } else { "" } , ) ;
119+ let replacement = format!(
120+ "{}{}?{}" ,
121+ receiver_str,
122+ if by_ref { ".as_ref()" } else { "" } ,
123+ if requires_semi( if_then, if_else) { ";" } else { "" }
124+ ) ;
116125 offer_suggestion( cx, expr, replacement, applicability) ;
117126 }
118127 }
@@ -125,14 +134,14 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
125134 // If the block could be identified as `if x.is_none()/is_err()`,
126135 // we then only need to check the if_then return to see if it is none/err.
127136 is_type_diagnostic_item ( cx, caller_ty, smbl)
128- && expr_return_none_or_err ( smbl, cx, if_then, caller)
137+ && expr_return_none_or_err ( smbl, cx, if_then, caller, None )
129138 && match smbl {
130139 sym:: Option => call_sym == sym ! ( is_none) ,
131140 sym:: Result => call_sym == sym ! ( is_err) ,
132141 _ => false ,
133142 }
134143 } ,
135- IfBlockType :: IfLet ( qpath, _ , let_expr, if_then, if_else) => {
144+ IfBlockType :: IfLet ( qpath, let_pat_sym , let_expr, if_then, if_else) => {
136145 let let_expr_ty = cx. typeck_results ( ) . expr_ty ( let_expr) ;
137146 is_type_diagnostic_item ( cx, let_expr_ty, smbl)
138147 && match smbl {
@@ -144,7 +153,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
144153 } else {
145154 return false ;
146155 } ;
147- expr_return_none_or_err ( smbl, cx, else_expr, let_expr) && is_lang_ctor ( cx, qpath, OptionSome )
156+ expr_return_none_or_err ( smbl, cx, else_expr, let_expr, None )
157+ && is_lang_ctor ( cx, qpath, OptionSome )
148158 } ,
149159 sym:: Result => {
150160 if is_lang_ctor ( cx, qpath, ResultOk ) {
@@ -153,9 +163,9 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
153163 } else {
154164 return false ;
155165 } ;
156- expr_return_none_or_err ( smbl, cx, else_expr, let_expr)
166+ expr_return_none_or_err ( smbl, cx, else_expr, let_expr, Some ( let_pat_sym ) )
157167 } else if is_lang_ctor ( cx, qpath, ResultErr ) {
158- expr_return_none_or_err ( smbl, cx, if_then, let_expr)
168+ expr_return_none_or_err ( smbl, cx, if_then, let_expr, Some ( let_pat_sym ) )
159169 } else {
160170 false
161171 }
@@ -166,18 +176,55 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
166176 }
167177}
168178
169- fn expr_return_none_or_err ( smbl : Symbol , cx : & LateContext < ' _ > , expr : & Expr < ' _ > , cond_expr : & Expr < ' _ > ) -> bool {
179+ fn expr_return_none_or_err (
180+ smbl : Symbol ,
181+ cx : & LateContext < ' _ > ,
182+ expr : & Expr < ' _ > ,
183+ cond_expr : & Expr < ' _ > ,
184+ err_sym : Option < Symbol > ,
185+ ) -> bool {
170186 match peel_blocks_with_stmt ( expr) . kind {
171- ExprKind :: Ret ( Some ( ret_expr) ) => expr_return_none_or_err ( smbl, cx, ret_expr, cond_expr) ,
187+ ExprKind :: Ret ( Some ( ret_expr) ) => expr_return_none_or_err ( smbl, cx, ret_expr, cond_expr, err_sym ) ,
172188 ExprKind :: Path ( ref qpath) => match smbl {
173189 sym:: Option => is_lang_ctor ( cx, qpath, OptionNone ) ,
174190 sym:: Result => path_to_local ( expr) . is_some ( ) && path_to_local ( expr) == path_to_local ( cond_expr) ,
175191 _ => false ,
176192 } ,
193+ ExprKind :: Call ( call_expr, args_expr) => {
194+ if_chain ! {
195+ if smbl == sym:: Result ;
196+ if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = & call_expr. kind;
197+ if let Some ( segment) = path. segments. first( ) ;
198+ if let Some ( err_sym) = err_sym;
199+ if let Some ( arg) = args_expr. first( ) ;
200+ if let ExprKind :: Path ( QPath :: Resolved ( _, arg_path) ) = & arg. kind;
201+ if let Some ( PathSegment { ident, .. } ) = arg_path. segments. first( ) ;
202+ then {
203+ return segment. ident. name == sym:: Err && err_sym == ident. name;
204+ }
205+ }
206+ false
207+ } ,
177208 _ => false ,
178209 }
179210}
180211
212+ /// Check whether the fixed code needs semicolon after `?`
213+ ///
214+ /// It will need a semicolon if all block(s) has one.
215+ fn requires_semi ( if_then : & Expr < ' _ > , if_else : Option < & Expr < ' _ > > ) -> bool {
216+ let if_then_kind = & peel_blocks_with_stmt ( if_then) . kind ;
217+ let if_then_is_ret_stmt = matches ! ( if_then_kind, ExprKind :: Ret ( ..) ) ;
218+
219+ if if_else. is_none ( ) {
220+ return if_then_is_ret_stmt;
221+ }
222+ if let ExprKind :: Ret ( _) = peel_blocks_with_stmt ( if_else. unwrap ( ) ) . kind {
223+ return if_then_is_ret_stmt;
224+ }
225+ false
226+ }
227+
181228fn offer_suggestion ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , suggestion : String , applicability : Applicability ) {
182229 if !suggestion. is_empty ( ) {
183230 span_lint_and_sugg (
0 commit comments