@@ -4,15 +4,15 @@ 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:: { get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item , path_to_local} ;
8
8
use rustc_ast:: ast:: RangeLimits ;
9
9
use rustc_errors:: Applicability ;
10
- use rustc_hir:: { BinOpKind , Expr , ExprKind , HirId } ;
10
+ use rustc_hir:: { BinOpKind , Expr , ExprKind , HirId , LangItem } ;
11
11
use rustc_lint:: { LateContext , LateLintPass } ;
12
12
use rustc_middle:: ty;
13
13
use rustc_session:: impl_lint_pass;
14
- use rustc_span:: Span ;
15
14
use rustc_span:: source_map:: Spanned ;
15
+ use rustc_span:: { Span , sym} ;
16
16
use std:: cmp:: Ordering ;
17
17
18
18
declare_clippy_lint ! {
@@ -24,6 +24,12 @@ declare_clippy_lint! {
24
24
/// The code is more readable with an inclusive range
25
25
/// like `x..=y`.
26
26
///
27
+ /// ### Limitations
28
+ /// The lint is conservative and will trigger only when switching
29
+ /// from an exclusive to an inclusive range is provably safe from
30
+ /// a typing point of view. This corresponds to situations where
31
+ /// the range is used as an iterator, or for indexing.
32
+ ///
27
33
/// ### Known problems
28
34
/// Will add unnecessary pair of parentheses when the
29
35
/// expression is not wrapped in a pair but starts with an opening parenthesis
@@ -34,11 +40,6 @@ declare_clippy_lint! {
34
40
/// exclusive ranges, because they essentially add an extra branch that
35
41
/// LLVM may fail to hoist out of the loop.
36
42
///
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
43
/// ### Example
43
44
/// ```no_run
44
45
/// # let x = 0;
@@ -71,11 +72,11 @@ declare_clippy_lint! {
71
72
/// The code is more readable with an exclusive range
72
73
/// like `x..y`.
73
74
///
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)) .
75
+ /// ### Limitations
76
+ /// The lint is conservative and will trigger only when switching
77
+ /// from an inclusive to an exclusive range is provably safe from
78
+ /// a typing point of view. This corresponds to situations where
79
+ /// the range is used as an iterator, or for indexing .
79
80
///
80
81
/// ### Example
81
82
/// ```no_run
@@ -344,6 +345,41 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) ->
344
345
None
345
346
}
346
347
348
+ /// Check whether `expr` could switch range types without breaking the typing requirements. This is
349
+ /// the case when `expr` is used as an iterator for example, or as a slice or `&str` index.
350
+ fn can_switch_ranges ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
351
+ let Some ( parent_expr) = get_parent_expr ( cx, expr) else {
352
+ return false ;
353
+ } ;
354
+
355
+ if let ExprKind :: Call ( func, [ arg] ) = parent_expr. kind
356
+ && arg. hir_id == expr. hir_id
357
+ && is_path_lang_item ( cx, func, LangItem :: IntoIterIntoIter )
358
+ {
359
+ return true ;
360
+ }
361
+
362
+ if let ExprKind :: MethodCall ( _, receiver, _, _) = parent_expr. kind
363
+ && receiver. hir_id == expr. hir_id
364
+ && let Some ( method_did) = cx. typeck_results ( ) . type_dependent_def_id ( parent_expr. hir_id )
365
+ && let Some ( trait_did) = cx. tcx . trait_of_item ( method_did)
366
+ && matches ! (
367
+ cx. tcx. get_diagnostic_name( trait_did) ,
368
+ Some ( sym:: Iterator | sym:: IntoIterator | sym:: RangeBounds )
369
+ )
370
+ {
371
+ return true ;
372
+ }
373
+
374
+ if let ExprKind :: Index ( _, index, _) = parent_expr. kind
375
+ && index. hir_id == expr. hir_id
376
+ {
377
+ return true ;
378
+ }
379
+
380
+ false
381
+ }
382
+
347
383
// exclusive range plus one: `x..(y+1)`
348
384
fn check_exclusive_range_plus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
349
385
if expr. span . can_be_used_for_suggestions ( )
@@ -353,6 +389,7 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
353
389
limits : RangeLimits :: HalfOpen ,
354
390
} ) = higher:: Range :: hir ( expr)
355
391
&& let Some ( y) = y_plus_one ( cx, end)
392
+ && can_switch_ranges ( cx, expr)
356
393
{
357
394
let span = expr. span ;
358
395
span_lint_and_then (
@@ -391,6 +428,7 @@ fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
391
428
limits : RangeLimits :: Closed ,
392
429
} ) = higher:: Range :: hir ( expr)
393
430
&& let Some ( y) = y_minus_one ( cx, end)
431
+ && can_switch_ranges ( cx, expr)
394
432
{
395
433
span_lint_and_then (
396
434
cx,
0 commit comments