@@ -3,8 +3,11 @@ use crate::{LateContext, LateLintPass, LintContext};
33use hir:: { Expr , Pat } ;
44use rustc_errors:: Applicability ;
55use rustc_hir as hir;
6- use rustc_middle:: ty;
7- use rustc_span:: sym;
6+ use rustc_infer:: traits:: TraitEngine ;
7+ use rustc_infer:: { infer:: TyCtxtInferExt , traits:: ObligationCause } ;
8+ use rustc_middle:: ty:: { self , List } ;
9+ use rustc_span:: { sym, Span } ;
10+ use rustc_trait_selection:: traits:: TraitEngineExt ;
811
912declare_lint ! {
1013 /// ### What it does
@@ -58,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopOverFallibles {
5861
5962 let ty = cx. typeck_results ( ) . expr_ty ( arg) ;
6063
61- let ty:: Adt ( adt, _ ) = ty. kind ( ) else { return } ;
64+ let & ty:: Adt ( adt, substs ) = ty. kind ( ) else { return } ;
6265
6366 let ( article, ty, var) = match adt. did ( ) {
6467 did if cx. tcx . is_diagnostic_item ( sym:: Option , did) => ( "an" , "Option" , "Some" ) ,
@@ -96,6 +99,15 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopOverFallibles {
9699 ) ;
97100 }
98101
102+ if suggest_question_mark ( cx, adt, substs, expr. span ) {
103+ warn. span_suggestion (
104+ arg. span . shrink_to_hi ( ) ,
105+ "consider unwrapping the `Result` with `?` to iterate over its contents" ,
106+ "?" ,
107+ Applicability :: MaybeIncorrect ,
108+ ) ;
109+ }
110+
99111 warn. multipart_suggestion_verbose (
100112 "consider using `if let` to clear intent" ,
101113 vec ! [
@@ -140,3 +152,53 @@ fn extract_iterator_next_call<'tcx>(
140152 return None
141153 }
142154}
155+
156+ fn suggest_question_mark < ' tcx > (
157+ cx : & LateContext < ' tcx > ,
158+ adt : ty:: AdtDef < ' tcx > ,
159+ substs : & List < ty:: GenericArg < ' tcx > > ,
160+ span : Span ,
161+ ) -> bool {
162+ let Some ( body_id) = cx. enclosing_body else { return false } ;
163+ let Some ( into_iterator_did) = cx. tcx . get_diagnostic_item ( sym:: IntoIterator ) else { return false } ;
164+
165+ if !cx. tcx . is_diagnostic_item ( sym:: Result , adt. did ( ) ) {
166+ return false ;
167+ }
168+
169+ // Check that the function/closure/constant we are in has a `Result` type.
170+ // Otherwise suggesting using `?` may not be a good idea.
171+ {
172+ let ty = cx. typeck_results ( ) . expr_ty ( & cx. tcx . hir ( ) . body ( body_id) . value ) ;
173+ let ty:: Adt ( ret_adt, ..) = ty. kind ( ) else { return false } ;
174+ if !cx. tcx . is_diagnostic_item ( sym:: Result , ret_adt. did ( ) ) {
175+ return false ;
176+ }
177+ }
178+
179+ let ty = substs. type_at ( 0 ) ;
180+ let is_iterator = cx. tcx . infer_ctxt ( ) . enter ( |infcx| {
181+ let mut fulfill_cx = <dyn TraitEngine < ' _ > >:: new ( infcx. tcx ) ;
182+
183+ let cause = ObligationCause :: new (
184+ span,
185+ body_id. hir_id ,
186+ rustc_infer:: traits:: ObligationCauseCode :: MiscObligation ,
187+ ) ;
188+ fulfill_cx. register_bound (
189+ & infcx,
190+ ty:: ParamEnv :: empty ( ) ,
191+ // Erase any region vids from the type, which may not be resolved
192+ infcx. tcx . erase_regions ( ty) ,
193+ into_iterator_did,
194+ cause,
195+ ) ;
196+
197+ // Select all, including ambiguous predicates
198+ let errors = fulfill_cx. select_all_or_error ( & infcx) ;
199+
200+ errors. is_empty ( )
201+ } ) ;
202+
203+ is_iterator
204+ }
0 commit comments