@@ -8,89 +8,95 @@ use rustc_errors::Applicability;
88use rustc_hir:: { Expr , ExprKind , LangItem , Path , QPath } ;
99use rustc_lint:: LateContext ;
1010use rustc_middle:: mir:: Const ;
11- use rustc_middle:: ty:: { Adt , Ty } ;
11+ use rustc_middle:: ty:: { Adt , Ty , TypeckResults } ;
1212use rustc_span:: Span ;
1313use rustc_span:: symbol:: sym;
1414
1515use super :: TRUNCATE_WITH_DRAIN ;
1616
1717// Add `String` here when it is added to diagnostic items
18- const ACCEPTABLE_TYPES : [ rustc_span:: Symbol ; 2 ] = [ sym:: Vec , sym:: VecDeque ] ;
18+ const ACCEPTABLE_TYPES_WITH_ARG : [ rustc_span:: Symbol ; 2 ] = [ sym:: Vec , sym:: VecDeque ] ;
1919
20- pub fn is_range_open_ended ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , container_path : Option < & Path < ' _ > > ) -> bool {
21- let ty = cx. typeck_results ( ) . expr_ty ( expr) ;
22- if let Some ( higher:: Range { start, end, limits } ) = higher:: Range :: hir ( expr) {
23- let start_is_none_or_min = start. is_none_or ( |start| {
20+ pub fn is_range_open_ended < ' a > (
21+ cx : & LateContext < ' a > ,
22+ range : higher:: Range < ' _ > ,
23+ ty : Ty < ' a > ,
24+ container_path : Option < & Path < ' _ > > ,
25+ ) -> bool {
26+ let higher:: Range { start, end, limits } = range;
27+ let start_is_none_or_min = start. map_or ( true , |start| {
28+ if let Adt ( _, subst) = ty. kind ( )
29+ && let bnd_ty = subst. type_at ( 0 )
30+ && let Some ( min_val) = bnd_ty. numeric_min_val ( cx. tcx )
31+ && let Some ( min_const) = mir_to_const ( cx. tcx , Const :: from_ty_const ( min_val, bnd_ty, cx. tcx ) )
32+ && let Some ( start_const) = ConstEvalCtxt :: new ( cx) . eval ( start)
33+ {
34+ start_const == min_const
35+ } else {
36+ false
37+ }
38+ } ) ;
39+ let end_is_none_or_max = end. map_or ( true , |end| match limits {
40+ RangeLimits :: Closed => {
2441 if let Adt ( _, subst) = ty. kind ( )
2542 && let bnd_ty = subst. type_at ( 0 )
26- && let Some ( min_val ) = bnd_ty. numeric_min_val ( cx. tcx )
27- && let Some ( min_const ) = mir_to_const ( cx. tcx , Const :: from_ty_const ( min_val , bnd_ty, cx. tcx ) )
28- && let Some ( start_const ) = ConstEvalCtxt :: new ( cx) . eval ( start )
43+ && let Some ( max_val ) = bnd_ty. numeric_max_val ( cx. tcx )
44+ && let Some ( max_const ) = mir_to_const ( cx. tcx , Const :: from_ty_const ( max_val , bnd_ty, cx. tcx ) )
45+ && let Some ( end_const ) = ConstEvalCtxt :: new ( cx) . eval ( end )
2946 {
30- start_const == min_const
47+ end_const == max_const
3148 } else {
3249 false
3350 }
34- } ) ;
35- let end_is_none_or_max = end. is_none_or ( |end| match limits {
36- RangeLimits :: Closed => {
37- if let Adt ( _, subst) = ty. kind ( )
38- && let bnd_ty = subst. type_at ( 0 )
39- && let Some ( max_val) = bnd_ty. numeric_max_val ( cx. tcx )
40- && let Some ( max_const) = mir_to_const ( cx. tcx , Const :: from_ty_const ( max_val, bnd_ty, cx. tcx ) )
41- && let Some ( end_const) = ConstEvalCtxt :: new ( cx) . eval ( end)
42- {
43- end_const == max_const
44- } else {
45- false
46- }
47- } ,
48- RangeLimits :: HalfOpen => {
49- if let Some ( container_path) = container_path
50- && let ExprKind :: MethodCall ( name, self_arg, [ ] , _) = end. kind
51- && name. ident . name == sym:: len
52- && let ExprKind :: Path ( QPath :: Resolved ( None , path) ) = self_arg. kind
53- {
54- container_path. res == path. res
55- } else {
56- false
57- }
58- } ,
59- } ) ;
60- return !start_is_none_or_min && end_is_none_or_max;
61- }
62- false
63- }
64-
65- fn is_handled_collection_type ( cx : & LateContext < ' _ > , expr_ty : Ty < ' _ > ) -> bool {
66- ACCEPTABLE_TYPES . iter ( ) . any ( |& ty| is_type_diagnostic_item ( cx, expr_ty, ty) )
67- // String type is a lang item but not a diagnostic item for now so we need a separate check
68- || is_type_lang_item ( cx, expr_ty, LangItem :: String )
51+ } ,
52+ RangeLimits :: HalfOpen => {
53+ if let Some ( container_path) = container_path
54+ && let ExprKind :: MethodCall ( name, self_arg, [ ] , _) = end. kind
55+ && name. ident . name == sym:: len
56+ && let ExprKind :: Path ( QPath :: Resolved ( None , path) ) = self_arg. kind
57+ {
58+ container_path. res == path. res
59+ } else {
60+ false
61+ }
62+ } ,
63+ } ) ;
64+ return !start_is_none_or_min && end_is_none_or_max;
6965}
7066
7167pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , recv : & Expr < ' _ > , span : Span , arg : Option < & Expr < ' _ > > ) {
7268 if let Some ( arg) = arg {
73- let expr_ty = cx. typeck_results ( ) . expr_ty ( expr ) ;
74- if is_handled_collection_type ( cx, expr_ty )
69+ let typeck_results = cx. typeck_results ( ) ;
70+ if match_acceptable_type ( cx, recv , typeck_results , & ACCEPTABLE_TYPES_WITH_ARG )
7571 && let ExprKind :: Path ( QPath :: Resolved ( None , container_path) ) = recv. kind
76- && let Some ( range) = higher:: Range :: hir ( expr )
77- && is_range_open_ended ( cx , arg , Some ( container_path ) )
78- {
79- if let Some ( adt) = expr_ty. ty_adt_def ( )
72+ && let Some ( range) = higher:: Range :: hir ( arg )
73+ && let higher :: Range { start : Some ( start ) , .. } = range
74+ && is_range_open_ended ( cx , range , typeck_results . expr_ty ( arg ) , Some ( container_path ) )
75+ && let Some ( adt) = typeck_results . expr_ty ( recv ) . ty_adt_def ( )
8076 // Use `opt_item_name` while `String` is not a diagnostic item
8177 && let Some ( ty_name) = cx. tcx . opt_item_name ( adt. did ( ) )
82- {
83- span_lint_and_sugg (
84- cx,
85- TRUNCATE_WITH_DRAIN ,
86- span. with_hi ( expr. span . hi ( ) ) ,
87- format ! ( "`drain` used to truncate a `{ty_name}`" ) ,
88- "use" ,
89- // Can safely unwrap here because we don't lint on 0.. ranges
90- format ! ( "truncate({})" , snippet( cx, range. start. unwrap( ) . span, "0" ) ) ,
91- Applicability :: MachineApplicable ,
92- ) ;
93- }
78+ {
79+ span_lint_and_sugg (
80+ cx,
81+ TRUNCATE_WITH_DRAIN ,
82+ span. with_hi ( expr. span . hi ( ) ) ,
83+ format ! ( "`drain` used to truncate a `{ty_name}`" ) ,
84+ "try" ,
85+ format ! ( "truncate({})" , snippet( cx, start. span, "0" ) ) ,
86+ Applicability :: MachineApplicable ,
87+ ) ;
9488 }
9589 }
9690}
91+
92+ fn match_acceptable_type (
93+ cx : & LateContext < ' _ > ,
94+ expr : & Expr < ' _ > ,
95+ typeck_results : & TypeckResults < ' _ > ,
96+ types : & [ rustc_span:: Symbol ] ,
97+ ) -> bool {
98+ let expr_ty = typeck_results. expr_ty ( expr) . peel_refs ( ) ;
99+ types. iter ( ) . any ( |& ty| is_type_diagnostic_item ( cx, expr_ty, ty) )
100+ // String type is a lang item but not a diagnostic item for now so we need a separate check
101+ || is_type_lang_item ( cx, expr_ty, LangItem :: String )
102+ }
0 commit comments