Skip to content

Commit 28e887f

Browse files
committed
Auto merge of #12488 - Jacherr:issue-11525, r=llogiq
Disable `indexing_slicing` for custom Index impls Fixes #11525 Disables `indexing_slicing` for custom Index impls, specifically any implementations that also do not have a `get` method anywhere along the deref chain (so, for example, it still lints on Vec, which has its `get` method as part of the deref chain). Thanks `@y21` for pointing me in the right direction with a couple of handy util functions for deref chain and inherent methods, saved a headache there! changelog: FP: Disable `indexing_slicing` for custom Index impls
2 parents 0b598b6 + 1c117f1 commit 28e887f

File tree

5 files changed

+232
-54
lines changed

5 files changed

+232
-54
lines changed

clippy_lints/src/indexing_slicing.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
use clippy_utils::consts::{constant, Constant};
44
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
55
use clippy_utils::higher;
6+
use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
67
use rustc_ast::ast::RangeLimits;
78
use rustc_hir::{Expr, ExprKind};
89
use rustc_lint::{LateContext, LateLintPass};
9-
use rustc_middle::ty;
10+
use rustc_middle::ty::{self, Ty};
1011
use rustc_session::impl_lint_pass;
12+
use rustc_span::sym;
1113

1214
declare_clippy_lint! {
1315
/// ### What it does
@@ -104,7 +106,15 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
104106
return;
105107
}
106108

107-
if let ExprKind::Index(array, index, _) = &expr.kind {
109+
if let ExprKind::Index(array, index, _) = &expr.kind
110+
&& let expr_ty = cx.typeck_results().expr_ty(array)
111+
&& let mut deref = deref_chain(cx, expr_ty)
112+
&& deref.any(|l| {
113+
l.peel_refs().is_slice()
114+
|| l.peel_refs().is_array()
115+
|| ty_has_applicable_get_function(cx, l.peel_refs(), expr_ty, expr)
116+
})
117+
{
108118
let note = "the suggestion might not be applicable in constant blocks";
109119
let ty = cx.typeck_results().expr_ty(array).peel_refs();
110120
if let Some(range) = higher::Range::hir(index) {
@@ -231,3 +241,33 @@ fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u1
231241

232242
(start, end)
233243
}
244+
245+
/// Checks if the output Ty of the `get` method on this Ty (if any) matches the Ty returned by the
246+
/// indexing operation (if any).
247+
fn ty_has_applicable_get_function<'tcx>(
248+
cx: &LateContext<'tcx>,
249+
ty: Ty<'tcx>,
250+
array_ty: Ty<'tcx>,
251+
index_expr: &Expr<'_>,
252+
) -> bool {
253+
if let ty::Adt(_, _) = array_ty.kind()
254+
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym!(get)).map(|m| {
255+
cx.tcx
256+
.fn_sig(m.def_id)
257+
.skip_binder()
258+
.output()
259+
.skip_binder()
260+
})
261+
&& let ty::Adt(def, args) = get_output_ty.kind()
262+
&& cx.tcx.is_diagnostic_item(sym::Option, def.0.did)
263+
&& let Some(option_generic_param) = args.first()
264+
&& let generic_ty = option_generic_param.expect_ty().peel_refs()
265+
// FIXME: ideally this would handle type params and projections properly, for now just assume it's the same type
266+
&& (cx.typeck_results().expr_ty(index_expr).peel_refs() == generic_ty.peel_refs()
267+
|| matches!(generic_ty.peel_refs().kind(), ty::Param(_) | ty::Alias(_, _)))
268+
{
269+
true
270+
} else {
271+
false
272+
}
273+
}

clippy_lints/src/iter_without_into_iter.rs

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::get_parent_as_impl;
33
use clippy_utils::source::snippet;
4-
use clippy_utils::ty::{implements_trait, make_normalized_projection};
4+
use clippy_utils::ty::{deref_chain, get_adt_inherent_method, implements_trait, make_normalized_projection};
55
use rustc_ast::Mutability;
66
use rustc_errors::Applicability;
77
use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, ItemKind, TyKind};
88
use rustc_lint::{LateContext, LateLintPass, LintContext};
99
use rustc_middle::lint::in_external_macro;
1010
use rustc_middle::ty::{self, Ty};
1111
use rustc_session::declare_lint_pass;
12-
use rustc_span::{sym, Symbol};
13-
use std::iter;
12+
use rustc_span::sym;
1413

1514
declare_clippy_lint! {
1615
/// ### What it does
@@ -124,33 +123,6 @@ fn is_ty_exported(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
124123
.is_some_and(|did| cx.effective_visibilities.is_exported(did))
125124
}
126125

127-
/// Returns the deref chain of a type, starting with the type itself.
128-
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
129-
iter::successors(Some(ty), |&ty| {
130-
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
131-
&& implements_trait(cx, ty, deref_did, &[])
132-
{
133-
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
134-
} else {
135-
None
136-
}
137-
})
138-
}
139-
140-
fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
141-
if let Some(ty_did) = ty.ty_adt_def().map(ty::AdtDef::did) {
142-
cx.tcx.inherent_impls(ty_did).into_iter().flatten().any(|&did| {
143-
cx.tcx
144-
.associated_items(did)
145-
.filter_by_name_unhygienic(method_name)
146-
.next()
147-
.is_some_and(|item| item.kind == ty::AssocKind::Fn)
148-
})
149-
} else {
150-
false
151-
}
152-
}
153-
154126
impl LateLintPass<'_> for IterWithoutIntoIter {
155127
fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
156128
if !in_external_macro(cx.sess(), item.span)
@@ -167,7 +139,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
167139
}
168140
&& !deref_chain(cx, ty).any(|ty| {
169141
// We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method
170-
ty.peel_refs().is_slice() || adt_has_inherent_method(cx, ty, expected_method_name)
142+
ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some()
171143
})
172144
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
173145
if item.ident.name == sym!(IntoIter) {

clippy_utils/src/ty.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use rustc_middle::mir::ConstValue;
1717
use rustc_middle::traits::EvaluationResult;
1818
use rustc_middle::ty::layout::ValidityRequirement;
1919
use rustc_middle::ty::{
20-
self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
21-
GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
22-
TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
20+
self, AdtDef, AliasTy, AssocItem, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind,
21+
GenericArgsRef, GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable,
22+
TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
2323
};
2424
use rustc_span::symbol::Ident;
2525
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
@@ -1328,3 +1328,39 @@ pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>
13281328
pub fn is_manually_drop(ty: Ty<'_>) -> bool {
13291329
ty.ty_adt_def().map_or(false, AdtDef::is_manually_drop)
13301330
}
1331+
1332+
/// Returns the deref chain of a type, starting with the type itself.
1333+
pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
1334+
iter::successors(Some(ty), |&ty| {
1335+
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
1336+
&& implements_trait(cx, ty, deref_did, &[])
1337+
{
1338+
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
1339+
} else {
1340+
None
1341+
}
1342+
})
1343+
}
1344+
1345+
/// Checks if a Ty<'_> has some inherent method Symbol.
1346+
/// This does not look for impls in the type's `Deref::Target` type.
1347+
/// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`.
1348+
pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> {
1349+
if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) {
1350+
cx.tcx
1351+
.inherent_impls(ty_did)
1352+
.into_iter()
1353+
.flatten()
1354+
.map(|&did| {
1355+
cx.tcx
1356+
.associated_items(did)
1357+
.filter_by_name_unhygienic(method_name)
1358+
.next()
1359+
.filter(|item| item.kind == AssocKind::Fn)
1360+
})
1361+
.next()
1362+
.flatten()
1363+
} else {
1364+
None
1365+
}
1366+
}

tests/ui/indexing_slicing_slice.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,89 @@
22
// We also check the out_of_bounds_indexing lint here, because it lints similar things and
33
// we want to avoid false positives.
44
#![warn(clippy::out_of_bounds_indexing)]
5-
#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec)]
5+
#![allow(
6+
clippy::no_effect,
7+
clippy::unnecessary_operation,
8+
clippy::useless_vec,
9+
unused_must_use,
10+
unused
11+
)]
12+
#![warn(clippy::indexing_slicing)]
13+
14+
use std::ops::Index;
15+
16+
struct BoolMap<T> {
17+
false_value: T,
18+
true_value: T,
19+
}
20+
21+
impl<T> Index<bool> for BoolMap<T> {
22+
type Output = T;
23+
fn index(&self, index: bool) -> &T {
24+
if index { &self.true_value } else { &self.false_value }
25+
}
26+
}
27+
28+
struct BoolMapWithGet<T> {
29+
false_value: T,
30+
true_value: T,
31+
}
32+
33+
impl<T> Index<bool> for BoolMapWithGet<T> {
34+
type Output = T;
35+
fn index(&self, index: bool) -> &Self::Output {
36+
if index { &self.true_value } else { &self.false_value }
37+
}
38+
}
39+
40+
impl<T> BoolMapWithGet<T> {
41+
fn get(&self, index: bool) -> Option<&T> {
42+
if index {
43+
Some(&self.true_value)
44+
} else {
45+
Some(&self.false_value)
46+
}
47+
}
48+
}
49+
50+
struct S<T>(T);
51+
impl S<i32> {
52+
fn get() -> Option<i32> {
53+
unimplemented!()
54+
}
55+
}
56+
impl<T> Index<i32> for S<T> {
57+
type Output = T;
58+
fn index(&self, _index: i32) -> &Self::Output {
59+
&self.0
60+
}
61+
}
62+
63+
struct Y<T>(T);
64+
impl Y<i32> {
65+
fn get<U>() -> Option<U> {
66+
unimplemented!()
67+
}
68+
}
69+
impl<T> Index<i32> for Y<T> {
70+
type Output = T;
71+
fn index(&self, _index: i32) -> &Self::Output {
72+
&self.0
73+
}
74+
}
75+
76+
struct Z<T>(T);
77+
impl<T> Z<T> {
78+
fn get<T2>() -> T2 {
79+
unimplemented!()
80+
}
81+
}
82+
impl<T> Index<i32> for Z<T> {
83+
type Output = T;
84+
fn index(&self, _index: i32) -> &Self::Output {
85+
&self.0
86+
}
87+
}
688

789
fn main() {
890
let x = [1, 2, 3, 4];
@@ -51,4 +133,28 @@ fn main() {
51133
//~^ ERROR: slicing may panic
52134

53135
&v[..]; // Ok, should not produce stderr.
136+
137+
let map = BoolMap {
138+
false_value: 2,
139+
true_value: 4,
140+
};
141+
142+
map[true]; // Ok, because `get` does not exist (custom indexing)
143+
144+
let map_with_get = BoolMapWithGet {
145+
false_value: 2,
146+
true_value: 4,
147+
};
148+
149+
// Lint on this, because `get` does exist with same signature
150+
map_with_get[true];
151+
152+
let s = S::<i32>(1);
153+
s[0];
154+
155+
let y = Y::<i32>(1);
156+
y[0];
157+
158+
let z = Z::<i32>(1);
159+
z[0];
54160
}

0 commit comments

Comments
 (0)