Skip to content

Commit 77633ae

Browse files
committed
const_size_windows: Improve trait impl conflict check
Also: - Improve naming - More robust UCFS argument snippet generation - Derive traits for helper structs
1 parent 8f580d9 commit 77633ae

1 file changed

Lines changed: 75 additions & 46 deletions

File tree

clippy_lints/src/methods/const_size_windows.rs

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,53 @@ use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::msrvs::Msrv;
44
use clippy_utils::res::{MaybeDef as _, MaybeTypeckRes as _};
55
use clippy_utils::source::snippet_opt;
6-
use clippy_utils::ty::implements_trait;
6+
use clippy_utils::ty::{deref_chain, implements_trait};
77
use clippy_utils::visitors::is_const_evaluatable;
88
use clippy_utils::{contains_name, msrvs, sym};
99
use rustc_ast::LitKind;
1010
use rustc_data_structures::packed::Pu128;
1111
use rustc_errors::Applicability;
1212
use rustc_hir::def_id::DefId;
13-
use rustc_hir::{Expr, ExprKind, ImplItemKind, Item, ItemKind, Node, QPath, TraitFn, TraitItemKind};
13+
use rustc_hir::{Expr, ExprKind, ImplItemKind, Item, ItemKind, Node, QPath, TraitFn, TraitItemKind, UnOp};
1414
use rustc_lint::LateContext;
15-
use rustc_middle::ty;
16-
use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability};
1715
use rustc_middle::ty::{AssocTag, EarlyBinder, Ty, Unnormalized};
1816
use rustc_span::{Span, Symbol};
19-
use std::fmt::Display;
2017
use std::ops::Not as _;
2118

2219
const ARRAY_WINDOWS: Symbol = sym::array_windows;
2320
const WINDOWS: Symbol = sym::windows;
2421
const SLICE: Symbol = sym::slice;
2522

26-
enum SpanLocation {
27-
MethodCall,
28-
EntireExpression,
29-
}
30-
23+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3124
struct Suggestion {
3225
span_location: SpanLocation,
3326
code: String,
3427
destructuring_example: Option<String>,
3528
}
3629

37-
enum DestructuringExtent {
38-
Full,
39-
Partial,
30+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31+
enum SpanLocation {
32+
MethodCall,
33+
EntireExpression,
4034
}
4135

36+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4237
struct DestructuredArray {
4338
array_snippet: String,
4439
extent: DestructuringExtent,
4540
}
4641

42+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43+
enum DestructuringExtent {
44+
Full,
45+
Partial,
46+
}
47+
4748
pub(super) fn check<'tcx>(
4849
cx: &LateContext<'tcx>,
49-
expr: &Expr<'_>,
50-
recv: &Expr<'_>,
51-
size_arg: &'tcx Expr<'_>,
50+
expr: &'tcx Expr<'tcx>,
51+
recv: &'tcx Expr<'tcx>,
52+
size_arg: &'tcx Expr<'tcx>,
5253
call_span: Span,
5354
msrv: Msrv,
5455
) {
@@ -104,7 +105,11 @@ fn is_const_size_window<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, size_arg:
104105
is_slice_method_call && is_const_evaluatable(cx.tcx, cx.typeck_results(), size_arg)
105106
}
106107

107-
fn compute_suggestion(cx: &LateContext<'_>, recv: &Expr<'_>, size_arg: &Expr<'_>) -> Option<Suggestion> {
108+
fn compute_suggestion<'tcx>(
109+
cx: &LateContext<'tcx>,
110+
recv: &'tcx Expr<'tcx>,
111+
size_arg: &'tcx Expr<'tcx>,
112+
) -> Option<Suggestion> {
108113
let (recv_snippet, size_arg_snippet) = Option::zip(
109114
snippet_opt(cx, recv.span.source_callsite()),
110115
snippet_opt(cx, size_arg.span.source_callsite()),
@@ -116,14 +121,14 @@ fn compute_suggestion(cx: &LateContext<'_>, recv: &Expr<'_>, size_arg: &Expr<'_>
116121
size_arg_snippet
117122
};
118123

119-
let is_ucfs_required = is_array_windows_shadowed(cx, cx.typeck_results().expr_ty(recv).peel_refs());
124+
let is_ucfs_required = is_array_windows_method_call_ambiguous(cx, recv);
120125

121126
let (span_location, code) = if is_ucfs_required {
122127
(
123128
SpanLocation::EntireExpression,
124129
format!(
125130
"<[_]>::{ARRAY_WINDOWS}::<{size_generic_const_arg}>({})",
126-
add_ref_prefix_for_ucfs_if_required(cx, recv, &recv_snippet)
131+
normalize_expr_to_single_ref(cx, recv)?
127132
),
128133
)
129134
} else {
@@ -134,21 +139,20 @@ fn compute_suggestion(cx: &LateContext<'_>, recv: &Expr<'_>, size_arg: &Expr<'_>
134139
};
135140

136141
let destructuring_example =
137-
build_destructured_array(cx, size_arg).map(|DestructuredArray { array_snippet, extent }| {
142+
build_destructured_array(cx, size_arg).and_then(|DestructuredArray { array_snippet, extent }| {
138143
let iterator_expr_snippet = match extent {
139-
DestructuringExtent::Full if is_ucfs_required => format!(
140-
"<[_]>::{ARRAY_WINDOWS}({})",
141-
add_ref_prefix_for_ucfs_if_required(cx, recv, &recv_snippet)
142-
),
144+
DestructuringExtent::Full if is_ucfs_required => {
145+
format!("<[_]>::{ARRAY_WINDOWS}({})", normalize_expr_to_single_ref(cx, recv)?)
146+
},
143147
DestructuringExtent::Full => format!("{recv_snippet}.{ARRAY_WINDOWS}()"),
144148
DestructuringExtent::Partial if is_ucfs_required => format!(
145149
"<[_]>::{ARRAY_WINDOWS}::<{size_generic_const_arg}>({})",
146-
add_ref_prefix_for_ucfs_if_required(cx, recv, &recv_snippet)
150+
normalize_expr_to_single_ref(cx, recv)?
147151
),
148152
DestructuringExtent::Partial => format!("{recv_snippet}.{ARRAY_WINDOWS}::<{size_generic_const_arg}>()"),
149153
};
150154

151-
format!("for {array_snippet} in {iterator_expr_snippet}")
155+
Some(format!("for {array_snippet} in {iterator_expr_snippet}"))
152156
});
153157

154158
Some(Suggestion {
@@ -212,11 +216,26 @@ fn expr_needs_braces_in_const_arg(expr: &Expr<'_>) -> bool {
212216
}
213217
}
214218

215-
fn is_array_windows_shadowed<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
219+
/// We are overly careful with this check: we allow FP because our goal is never allowing FN.
220+
/// The worst-case scenario of being overly careful is that UCFS is suggested when it isn't strictly
221+
/// necessary.
222+
fn is_array_windows_method_call_ambiguous<'tcx>(cx: &LateContext<'tcx>, recv: &Expr<'_>) -> bool {
223+
let typeck_results = cx.typeck_results();
224+
let candidate_recv_types: Vec<_> = [typeck_results.expr_ty(recv), typeck_results.expr_ty_adjusted(recv)]
225+
.into_iter()
226+
.flat_map(|ty| deref_chain(cx, ty))
227+
.collect();
228+
229+
let recv_may_implements_trait = |trait_def_id: DefId| {
230+
candidate_recv_types
231+
.iter()
232+
.any(|&ty| implements_trait(cx, ty, trait_def_id, &[]))
233+
};
234+
216235
cx.tcx
217-
.all_traits_including_private()
236+
.visible_traits()
218237
.filter(|&trait_def_id| trait_declares_array_windows(cx, trait_def_id))
219-
.any(|trait_def_id| implements_trait(cx, ty, trait_def_id, &[]))
238+
.any(recv_may_implements_trait)
220239
}
221240

222241
fn trait_declares_array_windows(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
@@ -226,23 +245,6 @@ fn trait_declares_array_windows(cx: &LateContext<'_>, trait_def_id: DefId) -> bo
226245
.any(|item| item.tag() == AssocTag::Fn)
227246
}
228247

229-
fn is_ref_prefix_required_for_ucfs(cx: &LateContext<'_>, recv: &Expr<'_>) -> bool {
230-
!matches!(cx.typeck_results().expr_ty(recv).kind(), ty::Ref(..))
231-
&& cx
232-
.typeck_results()
233-
.expr_adjustments(recv)
234-
.iter()
235-
.any(|adj| matches!(adj.kind, Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Not))))
236-
}
237-
238-
fn add_ref_prefix_for_ucfs_if_required(cx: &LateContext<'_>, recv: &Expr<'_>, recv_snippet: impl Display) -> String {
239-
if is_ref_prefix_required_for_ucfs(cx, recv) {
240-
format!("&{recv_snippet}")
241-
} else {
242-
recv_snippet.to_string()
243-
}
244-
}
245-
246248
fn get_containing_fn<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
247249
let parent_item = cx.tcx.hir_get_parent_item(expr.hir_id);
248250

@@ -264,3 +266,30 @@ fn get_containing_fn<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<&'
264266

265267
Some(cx.tcx.hir_body(body_id?).value)
266268
}
269+
270+
/// Attempt to produce a snippet of type &T by removing/adding `&` and `*` operators.
271+
/// If this is not possible, return `None`.
272+
/// Stops at macro expansion.
273+
///
274+
/// Examples:
275+
/// if `expr` is of type `T` -> `&expr`
276+
/// if `expr` is of type `&T` -> `expr`
277+
/// if `expr` is of type `&&T` -> `*expr`
278+
fn normalize_expr_to_single_ref(cx: &LateContext<'_>, recv: &Expr<'_>) -> Option<String> {
279+
// Peel & and * until we reach macro expansion or core referent
280+
let mut referent = recv;
281+
while !referent.span.from_expansion()
282+
&& let ExprKind::AddrOf(_, _, inner) | ExprKind::Unary(UnOp::Deref, inner) = referent.kind
283+
{
284+
referent = inner;
285+
}
286+
287+
let referent_snippet = snippet_opt(cx, referent.span.source_callsite())?;
288+
let snippet = if cx.typeck_results().expr_ty(referent).is_ref() {
289+
format!("{referent_snippet}")
290+
} else {
291+
format!("&{referent_snippet}")
292+
};
293+
294+
Some(snippet)
295+
}

0 commit comments

Comments
 (0)