@@ -4,11 +4,12 @@ use clippy_utils::msrvs::{self, Msrv};
44use clippy_utils:: peel_blocks;
55use clippy_utils:: source:: snippet_with_context;
66use clippy_utils:: ty:: is_type_diagnostic_item;
7- use clippy_utils:: visitors:: { for_each_expr , Descend } ;
7+ use clippy_utils:: visitors:: { Descend , Visitable } ;
88use if_chain:: if_chain;
99use rustc_data_structures:: fx:: FxHashSet ;
1010use rustc_errors:: Applicability ;
11- use rustc_hir:: { Expr , ExprKind , MatchSource , Pat , PatKind , QPath , Stmt , StmtKind } ;
11+ use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
12+ use rustc_hir:: { Expr , ExprKind , HirId , ItemId , Local , MatchSource , Pat , PatKind , QPath , Stmt , StmtKind , Ty } ;
1213use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1314use rustc_middle:: lint:: in_external_macro;
1415use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
@@ -162,61 +163,102 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat:
162163 ) ;
163164}
164165
165- fn expr_diverges ( cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) -> bool {
166- fn is_never ( cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) -> bool {
167- if let Some ( ty) = cx. typeck_results ( ) . expr_ty_opt ( expr) {
168- return ty. is_never ( ) ;
169- }
170- false
166+ /// Check whether an expression is divergent. May give false negatives.
167+ fn expr_diverges ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
168+ struct V < ' cx , ' tcx > {
169+ cx : & ' cx LateContext < ' tcx > ,
170+ res : ControlFlow < ( ) , Descend > ,
171171 }
172- // We can't just call is_never on expr and be done, because the type system
173- // sometimes coerces the ! type to something different before we can get
174- // our hands on it. So instead, we do a manual search. We do fall back to
175- // is_never in some places when there is no better alternative.
176- for_each_expr ( expr, |ex| {
177- match ex. kind {
178- ExprKind :: Continue ( _) | ExprKind :: Break ( _, _) | ExprKind :: Ret ( _) => ControlFlow :: Break ( ( ) ) ,
179- ExprKind :: Call ( call, _) => {
180- if is_never ( cx, ex) || is_never ( cx, call) {
181- return ControlFlow :: Break ( ( ) ) ;
182- }
183- ControlFlow :: Continue ( Descend :: Yes )
184- } ,
185- ExprKind :: MethodCall ( ..) => {
186- if is_never ( cx, ex) {
187- return ControlFlow :: Break ( ( ) ) ;
188- }
189- ControlFlow :: Continue ( Descend :: Yes )
190- } ,
191- ExprKind :: If ( if_expr, if_then, if_else) => {
192- let else_diverges = if_else. map_or ( false , |ex| expr_diverges ( cx, ex) ) ;
193- let diverges = expr_diverges ( cx, if_expr) || ( else_diverges && expr_diverges ( cx, if_then) ) ;
194- if diverges {
195- return ControlFlow :: Break ( ( ) ) ;
196- }
197- ControlFlow :: Continue ( Descend :: No )
198- } ,
199- ExprKind :: Match ( match_expr, match_arms, _) => {
200- let diverges = expr_diverges ( cx, match_expr)
201- || match_arms. iter ( ) . all ( |arm| {
202- let guard_diverges = arm. guard . as_ref ( ) . map_or ( false , |g| expr_diverges ( cx, g. body ( ) ) ) ;
203- guard_diverges || expr_diverges ( cx, arm. body )
204- } ) ;
205- if diverges {
206- return ControlFlow :: Break ( ( ) ) ;
172+ impl < ' tcx > Visitor < ' tcx > for V < ' _ , ' _ > {
173+ fn visit_expr ( & mut self , e : & ' tcx Expr < ' tcx > ) {
174+ fn is_never ( cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) -> bool {
175+ if let Some ( ty) = cx. typeck_results ( ) . expr_ty_opt ( expr) {
176+ return ty. is_never ( ) ;
207177 }
208- ControlFlow :: Continue ( Descend :: No )
209- } ,
178+ false
179+ }
180+
181+ if self . res . is_break ( ) {
182+ return ;
183+ }
210184
211- // Don't continue into loops or labeled blocks, as they are breakable,
212- // and we'd have to start checking labels.
213- ExprKind :: Block ( _, Some ( _) ) | ExprKind :: Loop ( ..) => ControlFlow :: Continue ( Descend :: No ) ,
185+ // We can't just call is_never on expr and be done, because the type system
186+ // sometimes coerces the ! type to something different before we can get
187+ // our hands on it. So instead, we do a manual search. We do fall back to
188+ // is_never in some places when there is no better alternative.
189+ self . res = match e. kind {
190+ ExprKind :: Continue ( _) | ExprKind :: Break ( _, _) | ExprKind :: Ret ( _) => ControlFlow :: Break ( ( ) ) ,
191+ ExprKind :: Call ( call, _) => {
192+ if is_never ( self . cx , e) || is_never ( self . cx , call) {
193+ ControlFlow :: Break ( ( ) )
194+ } else {
195+ ControlFlow :: Continue ( Descend :: Yes )
196+ }
197+ } ,
198+ ExprKind :: MethodCall ( ..) => {
199+ if is_never ( self . cx , e) {
200+ ControlFlow :: Break ( ( ) )
201+ } else {
202+ ControlFlow :: Continue ( Descend :: Yes )
203+ }
204+ } ,
205+ ExprKind :: If ( if_expr, if_then, if_else) => {
206+ let else_diverges = if_else. map_or ( false , |ex| expr_diverges ( self . cx , ex) ) ;
207+ let diverges =
208+ expr_diverges ( self . cx , if_expr) || ( else_diverges && expr_diverges ( self . cx , if_then) ) ;
209+ if diverges {
210+ ControlFlow :: Break ( ( ) )
211+ } else {
212+ ControlFlow :: Continue ( Descend :: No )
213+ }
214+ } ,
215+ ExprKind :: Match ( match_expr, match_arms, _) => {
216+ let diverges = expr_diverges ( self . cx , match_expr)
217+ || match_arms. iter ( ) . all ( |arm| {
218+ let guard_diverges = arm. guard . as_ref ( ) . map_or ( false , |g| expr_diverges ( self . cx , g. body ( ) ) ) ;
219+ guard_diverges || expr_diverges ( self . cx , arm. body )
220+ } ) ;
221+ if diverges {
222+ ControlFlow :: Break ( ( ) )
223+ } else {
224+ ControlFlow :: Continue ( Descend :: No )
225+ }
226+ } ,
214227
215- // Default: descend
216- _ => ControlFlow :: Continue ( Descend :: Yes ) ,
228+ // Don't continue into loops or labeled blocks, as they are breakable,
229+ // and we'd have to start checking labels.
230+ ExprKind :: Block ( _, Some ( _) ) | ExprKind :: Loop ( ..) => ControlFlow :: Continue ( Descend :: No ) ,
231+
232+ // Default: descend
233+ _ => ControlFlow :: Continue ( Descend :: Yes ) ,
234+ } ;
235+ if let ControlFlow :: Continue ( Descend :: Yes ) = self . res {
236+ walk_expr ( self , e) ;
237+ }
238+ }
239+
240+ fn visit_local ( & mut self , local : & ' tcx Local < ' _ > ) {
241+ // Don't visit the else block of a let/else statement as it will not make
242+ // the statement divergent even though the else block is divergent.
243+ if let Some ( init) = local. init {
244+ self . visit_expr ( init) ;
245+ }
217246 }
218- } )
219- . is_some ( )
247+
248+ // Avoid unnecessary `walk_*` calls.
249+ fn visit_ty ( & mut self , _: & ' tcx Ty < ' tcx > ) { }
250+ fn visit_pat ( & mut self , _: & ' tcx Pat < ' tcx > ) { }
251+ fn visit_qpath ( & mut self , _: & ' tcx QPath < ' tcx > , _: HirId , _: Span ) { }
252+ // Avoid monomorphising all `visit_*` functions.
253+ fn visit_nested_item ( & mut self , _: ItemId ) { }
254+ }
255+
256+ let mut v = V {
257+ cx,
258+ res : ControlFlow :: Continue ( Descend :: Yes ) ,
259+ } ;
260+ expr. visit ( & mut v) ;
261+ v. res . is_break ( )
220262}
221263
222264fn pat_allowed_for_else ( cx : & LateContext < ' _ > , pat : & ' _ Pat < ' _ > , check_types : bool ) -> bool {
0 commit comments