@@ -4,15 +4,19 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
4
4
use clippy_utils:: msrvs:: { self , Msrv } ;
5
5
use clippy_utils:: source:: { SpanRangeExt , snippet, snippet_with_applicability} ;
6
6
use 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
+ fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item, path_to_local,
10
+ } ;
11
+ use rustc_ast:: Mutability ;
8
12
use rustc_ast:: ast:: RangeLimits ;
9
13
use rustc_errors:: Applicability ;
10
- use rustc_hir:: { BinOpKind , Expr , ExprKind , HirId } ;
14
+ use rustc_hir:: { BinOpKind , Expr , ExprKind , HirId , LangItem } ;
11
15
use rustc_lint:: { LateContext , LateLintPass } ;
12
- use rustc_middle:: ty;
16
+ use rustc_middle:: ty:: { self , ClauseKind , GenericArgKind , PredicatePolarity , Ty } ;
13
17
use rustc_session:: impl_lint_pass;
14
- use rustc_span:: Span ;
15
18
use rustc_span:: source_map:: Spanned ;
19
+ use rustc_span:: { Span , sym} ;
16
20
use std:: cmp:: Ordering ;
17
21
18
22
declare_clippy_lint ! {
@@ -24,6 +28,12 @@ declare_clippy_lint! {
24
28
/// The code is more readable with an inclusive range
25
29
/// like `x..=y`.
26
30
///
31
+ /// ### Limitations
32
+ /// The lint is conservative and will trigger only when switching
33
+ /// from an exclusive to an inclusive range is provably safe from
34
+ /// a typing point of view. This corresponds to situations where
35
+ /// the range is used as an iterator, or for indexing.
36
+ ///
27
37
/// ### Known problems
28
38
/// Will add unnecessary pair of parentheses when the
29
39
/// expression is not wrapped in a pair but starts with an opening parenthesis
@@ -34,11 +44,6 @@ declare_clippy_lint! {
34
44
/// exclusive ranges, because they essentially add an extra branch that
35
45
/// LLVM may fail to hoist out of the loop.
36
46
///
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
- ///
42
47
/// ### Example
43
48
/// ```no_run
44
49
/// # let x = 0;
@@ -71,11 +76,11 @@ declare_clippy_lint! {
71
76
/// The code is more readable with an exclusive range
72
77
/// like `x..y`.
73
78
///
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)) .
79
+ /// ### Limitations
80
+ /// The lint is conservative and will trigger only when switching
81
+ /// from an inclusive to an exclusive range is provably safe from
82
+ /// a typing point of view. This corresponds to situations where
83
+ /// the range is used as an iterator, or for indexing .
79
84
///
80
85
/// ### Example
81
86
/// ```no_run
@@ -344,6 +349,115 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) ->
344
349
None
345
350
}
346
351
352
+ /// Check whether `expr` could switch range types without breaking the typing requirements. This is
353
+ /// generally the case when `expr` is used as an iterator for example, or as a slice or `&str`
354
+ /// index.
355
+ ///
356
+ /// FIXME: Note that the current implementation may still return false positives. A proper fix would
357
+ /// check that the obligations are still satisfied after switching the range type.
358
+ fn can_switch_ranges < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' _ > , original : RangeLimits , inner_ty : Ty < ' tcx > ) -> bool {
359
+ let Some ( parent_expr) = get_parent_expr ( cx, expr) else {
360
+ return false ;
361
+ } ;
362
+
363
+ // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)`
364
+ if let ExprKind :: Call ( func, [ arg] ) = parent_expr. kind
365
+ && arg. hir_id == expr. hir_id
366
+ && is_path_lang_item ( cx, func, LangItem :: IntoIterIntoIter )
367
+ {
368
+ return true ;
369
+ }
370
+
371
+ // Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`,
372
+ // or `RangeBounds` traits.
373
+ if let ExprKind :: MethodCall ( _, receiver, _, _) = parent_expr. kind
374
+ && receiver. hir_id == expr. hir_id
375
+ && let Some ( method_did) = cx. typeck_results ( ) . type_dependent_def_id ( parent_expr. hir_id )
376
+ && let Some ( trait_did) = cx. tcx . trait_of_item ( method_did)
377
+ && matches ! (
378
+ cx. tcx. get_diagnostic_name( trait_did) ,
379
+ Some ( sym:: Iterator | sym:: IntoIterator | sym:: RangeBounds )
380
+ )
381
+ {
382
+ return true ;
383
+ }
384
+
385
+ // Check if `expr` is an argument of a call which requires an `Iterator`, `IntoIterator`,
386
+ // or `RangeBounds` trait.
387
+ if let ExprKind :: Call ( _, args) | ExprKind :: MethodCall ( _, _, args, _) = parent_expr. kind
388
+ && let Some ( id) = fn_def_id ( cx, parent_expr)
389
+ && let Some ( arg_idx) = args. iter ( ) . position ( |e| e. hir_id == expr. hir_id )
390
+ {
391
+ let input_idx = if matches ! ( parent_expr. kind, ExprKind :: MethodCall ( ..) ) {
392
+ arg_idx + 1
393
+ } else {
394
+ arg_idx
395
+ } ;
396
+ let inputs = cx
397
+ . tcx
398
+ . liberate_late_bound_regions ( id, cx. tcx . fn_sig ( id) . instantiate_identity ( ) )
399
+ . inputs ( ) ;
400
+ let expr_ty = inputs[ input_idx] ;
401
+ // Check that the `expr` type is present only once, otherwise modifying just one of them might be
402
+ // risky if they are referenced using the same generic type for example.
403
+ if inputs. iter ( ) . enumerate ( ) . all ( |( n, ty) |
404
+ n == input_idx
405
+ || !ty. walk ( ) . any ( |arg| matches ! ( arg. unpack( ) , GenericArgKind :: Type ( ty) if ty == expr_ty) ) )
406
+ // Look for a clause requiring `Iterator`, `IntoIterator`, or `RangeBounds`, and resolving to `expr_type`.
407
+ && cx
408
+ . tcx
409
+ . param_env ( id)
410
+ . caller_bounds ( )
411
+ . into_iter ( )
412
+ . any ( |p| {
413
+ if let ClauseKind :: Trait ( t) = p. kind ( ) . skip_binder ( )
414
+ && t. polarity == PredicatePolarity :: Positive
415
+ && matches ! (
416
+ cx. tcx. get_diagnostic_name( t. trait_ref. def_id) ,
417
+ Some ( sym:: Iterator | sym:: IntoIterator | sym:: RangeBounds )
418
+ )
419
+ {
420
+ t. self_ty ( ) == expr_ty
421
+ } else {
422
+ false
423
+ }
424
+ } )
425
+ {
426
+ return true ;
427
+ }
428
+ }
429
+
430
+ // Check if `expr` is used for indexing, and if the switched range type could be used
431
+ // as well.
432
+ if let ExprKind :: Index ( outer_expr, index, _) = parent_expr. kind
433
+ && index. hir_id == expr. hir_id
434
+ // Build the switched range type (for example `RangeInclusive<usize>`).
435
+ && let Some ( switched_range_def_id) = match original {
436
+ RangeLimits :: HalfOpen => cx. tcx . lang_items ( ) . range_inclusive_struct ( ) ,
437
+ RangeLimits :: Closed => cx. tcx . lang_items ( ) . range_struct ( ) ,
438
+ }
439
+ && let switched_range_ty = cx
440
+ . tcx
441
+ . type_of ( switched_range_def_id)
442
+ . instantiate ( cx. tcx , & [ inner_ty. into ( ) ] )
443
+ // Check that the switched range type can be used for indexing the original expression
444
+ // through the `Index` or `IndexMut` trait.
445
+ && let ty:: Ref ( _, outer_ty, mutability) = cx. typeck_results ( ) . expr_ty_adjusted ( outer_expr) . kind ( )
446
+ && let Some ( index_def_id) = match mutability {
447
+ Mutability :: Not => cx. tcx . lang_items ( ) . index_trait ( ) ,
448
+ Mutability :: Mut => cx. tcx . lang_items ( ) . index_mut_trait ( ) ,
449
+ }
450
+ && implements_trait ( cx, * outer_ty, index_def_id, & [ switched_range_ty. into ( ) ] )
451
+ // We could also check that the associated item of the `index_def_id` trait with the switched range type
452
+ // return the same type, but it is reasonable to expect so. We can't check that the result is identical
453
+ // in both `Index<Range<…>>` and `Index<RangeInclusive<…>>` anyway.
454
+ {
455
+ return true ;
456
+ }
457
+
458
+ false
459
+ }
460
+
347
461
// exclusive range plus one: `x..(y+1)`
348
462
fn check_exclusive_range_plus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
349
463
if expr. span . can_be_used_for_suggestions ( )
@@ -353,6 +467,7 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
353
467
limits : RangeLimits :: HalfOpen ,
354
468
} ) = higher:: Range :: hir ( expr)
355
469
&& let Some ( y) = y_plus_one ( cx, end)
470
+ && can_switch_ranges ( cx, expr, RangeLimits :: HalfOpen , cx. typeck_results ( ) . expr_ty ( y) )
356
471
{
357
472
let span = expr. span ;
358
473
span_lint_and_then (
@@ -391,6 +506,7 @@ fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
391
506
limits : RangeLimits :: Closed ,
392
507
} ) = higher:: Range :: hir ( expr)
393
508
&& let Some ( y) = y_minus_one ( cx, end)
509
+ && can_switch_ranges ( cx, expr, RangeLimits :: Closed , cx. typeck_results ( ) . expr_ty ( y) )
394
510
{
395
511
span_lint_and_then (
396
512
cx,
0 commit comments