Skip to content

Commit 8bc8af3

Browse files
committed
Improve projection predicate handling
1 parent 578dbc7 commit 8bc8af3

File tree

5 files changed

+158
-59
lines changed

5 files changed

+158
-59
lines changed

clippy_lints/src/dereference.rs

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
4-
use clippy_utils::ty::{contains_ty, expr_sig, is_copy, is_substs_for_type, peel_mid_ty_refs, variant_of_res};
4+
use clippy_utils::ty::{contains_ty, expr_sig, is_copy, peel_mid_ty_refs, variant_of_res};
55
use clippy_utils::{fn_def_id, get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
66
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
77
use rustc_data_structures::fx::FxIndexMap;
@@ -12,16 +12,19 @@ use rustc_hir::{
1212
GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind,
1313
Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp,
1414
};
15+
use rustc_index::bit_set::BitSet;
1516
use rustc_infer::infer::TyCtxtInferExt;
1617
use rustc_lint::{LateContext, LateLintPass};
1718
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
1819
use rustc_middle::ty::{
19-
self, subst::Subst, EarlyBinder, ParamTy, PredicateKind, TraitPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults,
20+
self, subst::Subst, EarlyBinder, FnSig, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty, TyCtxt,
21+
TypeVisitable, TypeckResults,
2022
};
2123
use rustc_session::{declare_tool_lint, impl_lint_pass};
2224
use rustc_span::{symbol::sym, Span, Symbol};
2325
use rustc_trait_selection::infer::InferCtxtExt as _;
2426
use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
27+
use std::collections::VecDeque;
2528

2629
declare_clippy_lint! {
2730
/// ### What it does
@@ -958,6 +961,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
958961
let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
959962

960963
let Some(callee_def_id) = fn_def_id(cx, parent) else { return Position::Other(precedence) };
964+
let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
961965
let substs_with_expr_ty = cx
962966
.typeck_results()
963967
.node_substs(if let ExprKind::Call(callee, _) = parent.kind {
@@ -967,11 +971,11 @@ fn needless_borrow_impl_arg_position<'tcx>(
967971
});
968972

969973
let predicates = cx.tcx.param_env(callee_def_id).caller_bounds();
970-
let trait_predicates = predicates
974+
let trait_predicates_for_param_ty = predicates
971975
.iter()
972976
.filter_map(|predicate| {
973977
if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
974-
&& is_substs_for_type(trait_predicate.trait_ref.substs, param_ty.to_ty(cx.tcx))
978+
&& trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
975979
{
976980
Some(trait_predicate)
977981
} else {
@@ -982,9 +986,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
982986
let projection_predicates = predicates
983987
.iter()
984988
.filter_map(|predicate| {
985-
if let PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder()
986-
&& is_substs_for_type(projection_predicate.projection_ty.substs, param_ty.to_ty(cx.tcx))
987-
{
989+
if let PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
988990
Some(projection_predicate)
989991
} else {
990992
None
@@ -993,57 +995,43 @@ fn needless_borrow_impl_arg_position<'tcx>(
993995
.collect::<Vec<_>>();
994996

995997
// If no traits were found, or only the `Sized` or `Any` traits were found, return.
996-
if trait_predicates.iter().all(|trait_predicate| {
998+
if trait_predicates_for_param_ty.iter().all(|trait_predicate| {
997999
let trait_def_id = trait_predicate.def_id();
9981000
Some(trait_def_id) == sized_trait_def_id || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
9991001
}) {
10001002
return Position::Other(precedence);
10011003
}
10021004

1003-
// If `param_ty` appears in more than one place, then substituting it with `T` instead of `&T` could
1004-
// cause a type error.
1005-
if cx
1006-
.tcx
1007-
.fn_sig(callee_def_id)
1008-
.skip_binder()
1009-
.inputs_and_output
1005+
let has_ref_mut_self_method = trait_predicates_for_param_ty
10101006
.iter()
1011-
.enumerate()
1012-
.any(|(i, ty)| i != arg_index && contains_ty(ty, param_ty.to_ty(cx.tcx)))
1013-
{
1014-
return Position::Other(precedence);
1015-
}
1007+
.any(|TraitPredicate { trait_ref, .. }| has_ref_mut_self_method(cx, trait_ref.def_id));
10161008

1017-
let check_referent = |referent| {
1009+
// `substs_with_referent_ty` can be constructed outside of `check_referent` because the same
1010+
// elements are modified each time `check_referent` is called.
1011+
let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
1012+
1013+
let mut check_referent = |referent| {
10181014
let referent_ty = cx.typeck_results().expr_ty(referent);
10191015

10201016
if !is_copy(cx, referent_ty) {
10211017
return false;
10221018
}
10231019

10241020
// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
1025-
if !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut))
1026-
&& trait_predicates
1027-
.iter()
1028-
.any(|TraitPredicate { trait_ref, .. }| has_ref_mut_self_method(cx, trait_ref.def_id))
1029-
{
1021+
if has_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
10301022
return false;
10311023
}
10321024

1033-
let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
1034-
substs_with_referent_ty[param_ty.index as usize] = ty::GenericArg::from(referent_ty);
1035-
for projection_predicate in &projection_predicates {
1036-
if let ty::Term::Ty(term_ty) = projection_predicate.term
1037-
&& let ty::Param(term_param_ty) = term_ty.kind()
1038-
{
1039-
let item_def_id = projection_predicate.projection_ty.item_def_id;
1040-
let assoc_item = cx.tcx.associated_item(item_def_id);
1041-
let projection = cx
1042-
.tcx
1043-
.mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(referent_ty, &[]));
1044-
let projected_ty = cx.tcx.normalize_erasing_regions(cx.param_env, projection);
1045-
substs_with_referent_ty[term_param_ty.index as usize] = ty::GenericArg::from(projected_ty);
1046-
}
1025+
if !replace_types(
1026+
cx,
1027+
param_ty,
1028+
referent_ty,
1029+
fn_sig,
1030+
arg_index,
1031+
&projection_predicates,
1032+
&mut substs_with_referent_ty,
1033+
) {
1034+
return false;
10471035
}
10481036

10491037
predicates.iter().all(|predicate| {
@@ -1085,6 +1073,62 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
10851073
})
10861074
}
10871075

1076+
// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
1077+
// projected type that is a type parameter. Returns `false` if replacing the types would have an
1078+
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
1079+
// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
1080+
fn replace_types<'tcx>(
1081+
cx: &LateContext<'tcx>,
1082+
param_ty: ParamTy,
1083+
new_ty: Ty<'tcx>,
1084+
fn_sig: FnSig<'tcx>,
1085+
arg_index: usize,
1086+
projection_predicates: &[ProjectionPredicate<'tcx>],
1087+
substs: &mut [ty::GenericArg<'tcx>],
1088+
) -> bool {
1089+
let mut replaced = BitSet::new_empty(substs.len());
1090+
1091+
let mut deque = VecDeque::with_capacity(substs.len());
1092+
deque.push_back((param_ty, new_ty));
1093+
1094+
while let Some((param_ty, new_ty)) = deque.pop_front() {
1095+
let new_generic_arg = ty::GenericArg::from(new_ty);
1096+
1097+
// If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
1098+
if !(substs[param_ty.index as usize] == new_generic_arg
1099+
|| fn_sig
1100+
.inputs_and_output
1101+
.iter()
1102+
.enumerate()
1103+
.all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !contains_ty(ty, param_ty.to_ty(cx.tcx))))
1104+
{
1105+
return false;
1106+
}
1107+
1108+
// The next line should not panic, but panicking seems better than going into an infinite loop.
1109+
assert!(replaced.insert(param_ty.index));
1110+
1111+
substs[param_ty.index as usize] = new_generic_arg;
1112+
1113+
for projection_predicate in projection_predicates {
1114+
if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
1115+
&& let ty::Term::Ty(term_ty) = projection_predicate.term
1116+
&& let ty::Param(term_param_ty) = term_ty.kind()
1117+
{
1118+
let item_def_id = projection_predicate.projection_ty.item_def_id;
1119+
let assoc_item = cx.tcx.associated_item(item_def_id);
1120+
let projection = cx.tcx
1121+
.mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(new_ty, &[]));
1122+
let projected_ty = cx.tcx.normalize_erasing_regions(cx.param_env, projection);
1123+
1124+
deque.push_back((*term_param_ty, projected_ty));
1125+
}
1126+
}
1127+
}
1128+
1129+
true
1130+
}
1131+
10881132
// Checks whether a type is stable when switching to auto dereferencing,
10891133
fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position {
10901134
let ty::Ref(_, mut ty, _) = *ty.kind() else {

clippy_utils/src/ty.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety};
1212
use rustc_infer::infer::TyCtxtInferExt;
1313
use rustc_lint::LateContext;
1414
use rustc_middle::mir::interpret::{ConstValue, Scalar};
15-
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst, SubstsRef};
15+
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
1616
use rustc_middle::ty::{
1717
self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind,
1818
ProjectionPredicate, ProjectionTy, Region, RegionKind, TraitPredicate, Ty, TyCtxt, TypeSuperVisitable,
@@ -97,12 +97,12 @@ pub fn get_input_traits_and_projections<'tcx>(
9797
for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
9898
match predicate.kind().skip_binder() {
9999
PredicateKind::Trait(trait_predicate) => {
100-
if is_substs_for_type(trait_predicate.trait_ref.substs, input) {
100+
if trait_predicate.trait_ref.self_ty() == input {
101101
trait_predicates.push(trait_predicate);
102102
}
103103
},
104104
PredicateKind::Projection(projection_predicate) => {
105-
if is_substs_for_type(projection_predicate.projection_ty.substs, input) {
105+
if projection_predicate.projection_ty.self_ty() == input {
106106
projection_predicates.push(projection_predicate);
107107
}
108108
},
@@ -314,19 +314,6 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
314314
}
315315
}
316316

317-
// Returns `true` if `substs` has 1 + n elements and the first is `ty`. Think of the first as the
318-
// type on the left hand side of an `as`, and the rest as trait parameters.
319-
pub fn is_substs_for_type<'tcx>(substs: SubstsRef<'tcx>, ty: Ty<'tcx>) -> bool {
320-
if let Some(arg) = substs.iter().next()
321-
&& let GenericArgKind::Type(arg_ty) = arg.unpack()
322-
&& arg_ty == ty
323-
{
324-
true
325-
} else {
326-
false
327-
}
328-
}
329-
330317
/// Checks if the type is a reference equals to a diagnostic item
331318
pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
332319
match ty.kind() {

tests/ui/needless_borrow.fixed

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,14 @@ fn main() {
131131
let _ = std::process::Command::new("ls").args(["-a", "-l"]).status().unwrap();
132132
let _ = std::path::Path::new(".").join(".");
133133
deref_target_is_x(X);
134+
multiple_constraints([[""]]);
135+
multiple_constraints_normalizes_to_same(X, X);
134136

135137
only_sized(&""); // Don't lint. `Sized` is only bound
136138
let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound
137139
let _ = Box::new(&""); // Don't lint. Type parameter appears in return type
138140
ref_as_ref_path(&""); // Don't lint. Argument type is not a type parameter
141+
multiple_constraints_does_not_normalize_to_same(&[[""]], &[""]); // Don't lint. Projected type appears in arguments
139142
}
140143

141144
#[allow(clippy::needless_borrowed_reference)]
@@ -209,6 +212,23 @@ where
209212
{
210213
}
211214

215+
fn multiple_constraints<T, U, V, X, Y>(_: T)
216+
where
217+
T: IntoIterator<Item = U> + IntoIterator<Item = X>,
218+
U: IntoIterator<Item = V>,
219+
V: AsRef<str>,
220+
X: IntoIterator<Item = Y>,
221+
Y: AsRef<std::ffi::OsStr>,
222+
{
223+
}
224+
225+
fn multiple_constraints_normalizes_to_same<T, U, V>(_: T, _: V)
226+
where
227+
T: std::ops::Deref<Target = U>,
228+
U: std::ops::Deref<Target = V>,
229+
{
230+
}
231+
212232
fn only_sized<T>(_: T) {}
213233

214234
fn ref_as_ref_path<T: 'static>(_: &'static T)
@@ -217,6 +237,14 @@ where
217237
{
218238
}
219239

240+
fn multiple_constraints_does_not_normalize_to_same<T, U, V>(_: T, _: U)
241+
where
242+
T: IntoIterator<Item = U>,
243+
U: IntoIterator<Item = V>,
244+
V: AsRef<str>,
245+
{
246+
}
247+
220248
// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
221249
#[allow(dead_code)]
222250
mod copyable_iterator {

tests/ui/needless_borrow.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,14 @@ fn main() {
131131
let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
132132
let _ = std::path::Path::new(".").join(&&".");
133133
deref_target_is_x(&X);
134+
multiple_constraints(&[[""]]);
135+
multiple_constraints_normalizes_to_same(&X, X);
134136

135137
only_sized(&""); // Don't lint. `Sized` is only bound
136138
let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound
137139
let _ = Box::new(&""); // Don't lint. Type parameter appears in return type
138140
ref_as_ref_path(&""); // Don't lint. Argument type is not a type parameter
141+
multiple_constraints_does_not_normalize_to_same(&[[""]], &[""]); // Don't lint. Projected type appears in arguments
139142
}
140143

141144
#[allow(clippy::needless_borrowed_reference)]
@@ -209,6 +212,23 @@ where
209212
{
210213
}
211214

215+
fn multiple_constraints<T, U, V, X, Y>(_: T)
216+
where
217+
T: IntoIterator<Item = U> + IntoIterator<Item = X>,
218+
U: IntoIterator<Item = V>,
219+
V: AsRef<str>,
220+
X: IntoIterator<Item = Y>,
221+
Y: AsRef<std::ffi::OsStr>,
222+
{
223+
}
224+
225+
fn multiple_constraints_normalizes_to_same<T, U, V>(_: T, _: V)
226+
where
227+
T: std::ops::Deref<Target = U>,
228+
U: std::ops::Deref<Target = V>,
229+
{
230+
}
231+
212232
fn only_sized<T>(_: T) {}
213233

214234
fn ref_as_ref_path<T: 'static>(_: &'static T)
@@ -217,6 +237,14 @@ where
217237
{
218238
}
219239

240+
fn multiple_constraints_does_not_normalize_to_same<T, U, V>(_: T, _: U)
241+
where
242+
T: IntoIterator<Item = U>,
243+
U: IntoIterator<Item = V>,
244+
V: AsRef<str>,
245+
{
246+
}
247+
220248
// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
221249
#[allow(dead_code)]
222250
mod copyable_iterator {

tests/ui/needless_borrow.stderr

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,29 @@ error: the borrowed expression implements the required traits
138138
LL | deref_target_is_x(&X);
139139
| ^^ help: change this to: `X`
140140

141+
error: the borrowed expression implements the required traits
142+
--> $DIR/needless_borrow.rs:134:26
143+
|
144+
LL | multiple_constraints(&[[""]]);
145+
| ^^^^^^^ help: change this to: `[[""]]`
146+
147+
error: the borrowed expression implements the required traits
148+
--> $DIR/needless_borrow.rs:135:45
149+
|
150+
LL | multiple_constraints_normalizes_to_same(&X, X);
151+
| ^^ help: change this to: `X`
152+
141153
error: this expression borrows a value the compiler would automatically borrow
142-
--> $DIR/needless_borrow.rs:182:13
154+
--> $DIR/needless_borrow.rs:185:13
143155
|
144156
LL | (&self.f)()
145157
| ^^^^^^^^^ help: change this to: `(self.f)`
146158

147159
error: this expression borrows a value the compiler would automatically borrow
148-
--> $DIR/needless_borrow.rs:191:13
160+
--> $DIR/needless_borrow.rs:194:13
149161
|
150162
LL | (&mut self.f)()
151163
| ^^^^^^^^^^^^^ help: change this to: `(self.f)`
152164

153-
error: aborting due to 25 previous errors
165+
error: aborting due to 27 previous errors
154166

0 commit comments

Comments
 (0)