@@ -4,15 +4,20 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
44use clippy_utils:: msrvs:: { self , Msrv } ;
55use clippy_utils:: source:: { SpanRangeExt , snippet, snippet_with_applicability} ;
66use clippy_utils:: sugg:: Sugg ;
7- use clippy_utils:: { get_parent_expr, higher, is_in_const_context, is_integer_const, path_to_local} ;
7+ use clippy_utils:: ty:: implements_trait;
8+ use clippy_utils:: {
9+ expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item,
10+ path_to_local,
11+ } ;
12+ use rustc_ast:: Mutability ;
813use rustc_ast:: ast:: RangeLimits ;
914use rustc_errors:: Applicability ;
10- use rustc_hir:: { BinOpKind , Expr , ExprKind , HirId } ;
15+ use rustc_hir:: { BinOpKind , Expr , ExprKind , HirId , LangItem , Node } ;
1116use rustc_lint:: { LateContext , LateLintPass } ;
12- use rustc_middle:: ty;
17+ use rustc_middle:: ty:: { self , ClauseKind , GenericArgKind , PredicatePolarity , Ty } ;
1318use rustc_session:: impl_lint_pass;
14- use rustc_span:: Span ;
1519use rustc_span:: source_map:: Spanned ;
20+ use rustc_span:: { Span , sym} ;
1621use std:: cmp:: Ordering ;
1722
1823declare_clippy_lint ! {
@@ -24,6 +29,12 @@ declare_clippy_lint! {
2429 /// The code is more readable with an inclusive range
2530 /// like `x..=y`.
2631 ///
32+ /// ### Limitations
33+ /// The lint is conservative and will trigger only when switching
34+ /// from an exclusive to an inclusive range is provably safe from
35+ /// a typing point of view. This corresponds to situations where
36+ /// the range is used as an iterator, or for indexing.
37+ ///
2738 /// ### Known problems
2839 /// Will add unnecessary pair of parentheses when the
2940 /// expression is not wrapped in a pair but starts with an opening parenthesis
@@ -34,11 +45,6 @@ declare_clippy_lint! {
3445 /// exclusive ranges, because they essentially add an extra branch that
3546 /// LLVM may fail to hoist out of the loop.
3647 ///
37- /// This will cause a warning that cannot be fixed if the consumer of the
38- /// range only accepts a specific range type, instead of the generic
39- /// `RangeBounds` trait
40- /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
41- ///
4248 /// ### Example
4349 /// ```no_run
4450 /// # let x = 0;
@@ -71,11 +77,11 @@ declare_clippy_lint! {
7177 /// The code is more readable with an exclusive range
7278 /// like `x..y`.
7379 ///
74- /// ### Known problems
75- /// This will cause a warning that cannot be fixed if
76- /// the consumer of the range only accepts a specific range type, instead of
77- /// the generic `RangeBounds` trait
78- /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)) .
80+ /// ### Limitations
81+ /// The lint is conservative and will trigger only when switching
82+ /// from an inclusive to an exclusive range is provably safe from
83+ /// a typing point of view. This corresponds to situations where
84+ /// the range is used as an iterator, or for indexing .
7985 ///
8086 /// ### Example
8187 /// ```no_run
@@ -344,15 +350,132 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) ->
344350 None
345351}
346352
353+ /// Check whether `expr` could switch range types without breaking the typing requirements. This is
354+ /// generally the case when `expr` is used as an iterator for example, or as a slice or `&str`
355+ /// index.
356+ ///
357+ /// FIXME: Note that the current implementation may still return false positives. A proper fix would
358+ /// check that the obligations are still satisfied after switching the range type.
359+ fn can_switch_ranges < ' tcx > (
360+ cx : & LateContext < ' tcx > ,
361+ expr : & ' tcx Expr < ' _ > ,
362+ original : RangeLimits ,
363+ inner_ty : Ty < ' tcx > ,
364+ ) -> bool {
365+ let use_ctxt = expr_use_ctxt ( cx, expr) ;
366+ let ( Node :: Expr ( parent_expr) , false ) = ( use_ctxt. node , use_ctxt. is_ty_unified ) else {
367+ return false ;
368+ } ;
369+
370+ // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)`
371+ if let ExprKind :: Call ( func, [ arg] ) = parent_expr. kind
372+ && arg. hir_id == use_ctxt. child_id
373+ && is_path_lang_item ( cx, func, LangItem :: IntoIterIntoIter )
374+ {
375+ return true ;
376+ }
377+
378+ // Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`,
379+ // or `RangeBounds` traits.
380+ if let ExprKind :: MethodCall ( _, receiver, _, _) = parent_expr. kind
381+ && receiver. hir_id == use_ctxt. child_id
382+ && let Some ( method_did) = cx. typeck_results ( ) . type_dependent_def_id ( parent_expr. hir_id )
383+ && let Some ( trait_did) = cx. tcx . trait_of_item ( method_did)
384+ && matches ! (
385+ cx. tcx. get_diagnostic_name( trait_did) ,
386+ Some ( sym:: Iterator | sym:: IntoIterator | sym:: RangeBounds )
387+ )
388+ {
389+ return true ;
390+ }
391+
392+ // Check if `expr` is an argument of a call which requires an `Iterator`, `IntoIterator`,
393+ // or `RangeBounds` trait.
394+ if let ExprKind :: Call ( _, args) | ExprKind :: MethodCall ( _, _, args, _) = parent_expr. kind
395+ && let Some ( id) = fn_def_id ( cx, parent_expr)
396+ && let Some ( arg_idx) = args. iter ( ) . position ( |e| e. hir_id == use_ctxt. child_id )
397+ {
398+ let input_idx = if matches ! ( parent_expr. kind, ExprKind :: MethodCall ( ..) ) {
399+ arg_idx + 1
400+ } else {
401+ arg_idx
402+ } ;
403+ let inputs = cx
404+ . tcx
405+ . liberate_late_bound_regions ( id, cx. tcx . fn_sig ( id) . instantiate_identity ( ) )
406+ . inputs ( ) ;
407+ let expr_ty = inputs[ input_idx] ;
408+ // Check that the `expr` type is present only once, otherwise modifying just one of them might be
409+ // risky if they are referenced using the same generic type for example.
410+ if inputs. iter ( ) . enumerate ( ) . all ( |( n, ty) |
411+ n == input_idx
412+ || !ty. walk ( ) . any ( |arg| matches ! ( arg. kind( ) ,
413+ GenericArgKind :: Type ( ty) if ty == expr_ty) ) )
414+ // Look for a clause requiring `Iterator`, `IntoIterator`, or `RangeBounds`, and resolving to `expr_type`.
415+ && cx
416+ . tcx
417+ . param_env ( id)
418+ . caller_bounds ( )
419+ . into_iter ( )
420+ . any ( |p| {
421+ if let ClauseKind :: Trait ( t) = p. kind ( ) . skip_binder ( )
422+ && t. polarity == PredicatePolarity :: Positive
423+ && matches ! (
424+ cx. tcx. get_diagnostic_name( t. trait_ref. def_id) ,
425+ Some ( sym:: Iterator | sym:: IntoIterator | sym:: RangeBounds )
426+ )
427+ {
428+ t. self_ty ( ) == expr_ty
429+ } else {
430+ false
431+ }
432+ } )
433+ {
434+ return true ;
435+ }
436+ }
437+
438+ // Check if `expr` is used for indexing, and if the switched range type could be used
439+ // as well.
440+ if let ExprKind :: Index ( outer_expr, index, _) = parent_expr. kind
441+ && index. hir_id == expr. hir_id
442+ // Build the switched range type (for example `RangeInclusive<usize>`).
443+ && let Some ( switched_range_def_id) = match original {
444+ RangeLimits :: HalfOpen => cx. tcx . lang_items ( ) . range_inclusive_struct ( ) ,
445+ RangeLimits :: Closed => cx. tcx . lang_items ( ) . range_struct ( ) ,
446+ }
447+ && let switched_range_ty = cx
448+ . tcx
449+ . type_of ( switched_range_def_id)
450+ . instantiate ( cx. tcx , & [ inner_ty. into ( ) ] )
451+ // Check that the switched range type can be used for indexing the original expression
452+ // through the `Index` or `IndexMut` trait.
453+ && let ty:: Ref ( _, outer_ty, mutability) = cx. typeck_results ( ) . expr_ty_adjusted ( outer_expr) . kind ( )
454+ && let Some ( index_def_id) = match mutability {
455+ Mutability :: Not => cx. tcx . lang_items ( ) . index_trait ( ) ,
456+ Mutability :: Mut => cx. tcx . lang_items ( ) . index_mut_trait ( ) ,
457+ }
458+ && implements_trait ( cx, * outer_ty, index_def_id, & [ switched_range_ty. into ( ) ] )
459+ // We could also check that the associated item of the `index_def_id` trait with the switched range type
460+ // return the same type, but it is reasonable to expect so. We can't check that the result is identical
461+ // in both `Index<Range<…>>` and `Index<RangeInclusive<…>>` anyway.
462+ {
463+ return true ;
464+ }
465+
466+ false
467+ }
468+
347469// exclusive range plus one: `x..(y+1)`
348- fn check_exclusive_range_plus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
470+ fn check_exclusive_range_plus_one < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
349471 if expr. span . can_be_used_for_suggestions ( )
350472 && let Some ( higher:: Range {
351473 start,
352474 end : Some ( end) ,
353475 limits : RangeLimits :: HalfOpen ,
354476 } ) = higher:: Range :: hir ( expr)
355477 && let Some ( y) = y_plus_one ( cx, end)
478+ && can_switch_ranges ( cx, expr, RangeLimits :: HalfOpen , cx. typeck_results ( ) . expr_ty ( y) )
356479 {
357480 let span = expr. span ;
358481 span_lint_and_then (
@@ -383,14 +506,15 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
383506}
384507
385508// inclusive range minus one: `x..=(y-1)`
386- fn check_inclusive_range_minus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
509+ fn check_inclusive_range_minus_one < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
387510 if expr. span . can_be_used_for_suggestions ( )
388511 && let Some ( higher:: Range {
389512 start,
390513 end : Some ( end) ,
391514 limits : RangeLimits :: Closed ,
392515 } ) = higher:: Range :: hir ( expr)
393516 && let Some ( y) = y_minus_one ( cx, end)
517+ && can_switch_ranges ( cx, expr, RangeLimits :: Closed , cx. typeck_results ( ) . expr_ty ( y) )
394518 {
395519 span_lint_and_then (
396520 cx,
0 commit comments