Skip to content

Commit 6cfd4ac

Browse files
committed
Auto merge of #10632 - Alexendoo:needless-maybe-sized, r=Jarcho
Add `needless_maybe_sized` lint changelog: new lint: [`needless_maybe_sized`] Closes #10600
2 parents d955d0a + cf0b55e commit 6cfd4ac

12 files changed

+771
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5577,6 +5577,7 @@ Released 2018-09-13
55775577
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
55785578
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
55795579
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
5580+
[`needless_maybe_sized`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_maybe_sized
55805581
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
55815582
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
55825583
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
530530
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
531531
crate::needless_if::NEEDLESS_IF_INFO,
532532
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
533+
crate::needless_maybe_sized::NEEDLESS_MAYBE_SIZED_INFO,
533534
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
534535
crate::needless_pass_by_ref_mut::NEEDLESS_PASS_BY_REF_MUT_INFO,
535536
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ mod needless_else;
251251
mod needless_for_each;
252252
mod needless_if;
253253
mod needless_late_init;
254+
mod needless_maybe_sized;
254255
mod needless_parens_on_range_literals;
255256
mod needless_pass_by_ref_mut;
256257
mod needless_pass_by_value;
@@ -1058,6 +1059,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10581059
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
10591060
store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
10601061
store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
1062+
store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized));
10611063
store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
10621064
store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
10631065
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_errors::Applicability;
3+
use rustc_hir::def_id::{DefId, DefIdMap};
4+
use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifier, WherePredicate};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_middle::ty::{ClauseKind, PredicatePolarity};
7+
use rustc_session::declare_lint_pass;
8+
use rustc_span::symbol::Ident;
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Lints `?Sized` bounds applied to type parameters that cannot be unsized
13+
///
14+
/// ### Why is this bad?
15+
/// The `?Sized` bound is misleading because it cannot be satisfied by an
16+
/// unsized type
17+
///
18+
/// ### Example
19+
/// ```rust
20+
/// // `T` cannot be unsized because `Clone` requires it to be `Sized`
21+
/// fn f<T: Clone + ?Sized>(t: &T) {}
22+
/// ```
23+
/// Use instead:
24+
/// ```rust
25+
/// fn f<T: Clone>(t: &T) {}
26+
///
27+
/// // or choose alternative bounds for `T` so that it can be unsized
28+
/// ```
29+
#[clippy::version = "1.79.0"]
30+
pub NEEDLESS_MAYBE_SIZED,
31+
suspicious,
32+
"a `?Sized` bound that is unusable due to a `Sized` requirement"
33+
}
34+
declare_lint_pass!(NeedlessMaybeSized => [NEEDLESS_MAYBE_SIZED]);
35+
36+
#[allow(clippy::struct_field_names)]
37+
struct Bound<'tcx> {
38+
/// The [`DefId`] of the type parameter the bound refers to
39+
param: DefId,
40+
ident: Ident,
41+
42+
trait_bound: &'tcx PolyTraitRef<'tcx>,
43+
modifier: TraitBoundModifier,
44+
45+
predicate_pos: usize,
46+
bound_pos: usize,
47+
}
48+
49+
/// Finds all of the [`Bound`]s that refer to a type parameter and are not from a macro expansion
50+
fn type_param_bounds<'tcx>(generics: &'tcx Generics<'tcx>) -> impl Iterator<Item = Bound<'tcx>> {
51+
generics
52+
.predicates
53+
.iter()
54+
.enumerate()
55+
.filter_map(|(predicate_pos, predicate)| {
56+
let WherePredicate::BoundPredicate(bound_predicate) = predicate else {
57+
return None;
58+
};
59+
60+
let (param, ident) = bound_predicate.bounded_ty.as_generic_param()?;
61+
62+
Some(
63+
bound_predicate
64+
.bounds
65+
.iter()
66+
.enumerate()
67+
.filter_map(move |(bound_pos, bound)| match bound {
68+
&GenericBound::Trait(ref trait_bound, modifier) => Some(Bound {
69+
param,
70+
ident,
71+
trait_bound,
72+
modifier,
73+
predicate_pos,
74+
bound_pos,
75+
}),
76+
GenericBound::Outlives(_) => None,
77+
})
78+
.filter(|bound| !bound.trait_bound.span.from_expansion()),
79+
)
80+
})
81+
.flatten()
82+
}
83+
84+
/// Searches the supertraits of the trait referred to by `trait_bound` recursively, returning the
85+
/// path taken to find a `Sized` bound if one is found
86+
fn path_to_sized_bound(cx: &LateContext<'_>, trait_bound: &PolyTraitRef<'_>) -> Option<Vec<DefId>> {
87+
fn search(cx: &LateContext<'_>, path: &mut Vec<DefId>) -> bool {
88+
let trait_def_id = *path.last().unwrap();
89+
90+
if Some(trait_def_id) == cx.tcx.lang_items().sized_trait() {
91+
return true;
92+
}
93+
94+
for &(predicate, _) in cx.tcx.super_predicates_of(trait_def_id).predicates {
95+
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
96+
&& trait_predicate.polarity == PredicatePolarity::Positive
97+
&& !path.contains(&trait_predicate.def_id())
98+
{
99+
path.push(trait_predicate.def_id());
100+
if search(cx, path) {
101+
return true;
102+
}
103+
path.pop();
104+
}
105+
}
106+
107+
false
108+
}
109+
110+
let mut path = vec![trait_bound.trait_ref.trait_def_id()?];
111+
search(cx, &mut path).then_some(path)
112+
}
113+
114+
impl LateLintPass<'_> for NeedlessMaybeSized {
115+
fn check_generics(&mut self, cx: &LateContext<'_>, generics: &Generics<'_>) {
116+
let Some(sized_trait) = cx.tcx.lang_items().sized_trait() else {
117+
return;
118+
};
119+
120+
let maybe_sized_params: DefIdMap<_> = type_param_bounds(generics)
121+
.filter(|bound| {
122+
bound.trait_bound.trait_ref.trait_def_id() == Some(sized_trait)
123+
&& bound.modifier == TraitBoundModifier::Maybe
124+
})
125+
.map(|bound| (bound.param, bound))
126+
.collect();
127+
128+
for bound in type_param_bounds(generics) {
129+
if bound.modifier == TraitBoundModifier::None
130+
&& let Some(sized_bound) = maybe_sized_params.get(&bound.param)
131+
&& let Some(path) = path_to_sized_bound(cx, bound.trait_bound)
132+
{
133+
span_lint_and_then(
134+
cx,
135+
NEEDLESS_MAYBE_SIZED,
136+
sized_bound.trait_bound.span,
137+
"`?Sized` bound is ignored because of a `Sized` requirement",
138+
|diag| {
139+
let ty_param = sized_bound.ident;
140+
diag.span_note(
141+
bound.trait_bound.span,
142+
format!("`{ty_param}` cannot be unsized because of the bound"),
143+
);
144+
145+
for &[current_id, next_id] in path.array_windows() {
146+
let current = cx.tcx.item_name(current_id);
147+
let next = cx.tcx.item_name(next_id);
148+
diag.note(format!("...because `{current}` has the bound `{next}`"));
149+
}
150+
151+
diag.span_suggestion_verbose(
152+
generics.span_for_bound_removal(sized_bound.predicate_pos, sized_bound.bound_pos),
153+
"change the bounds that require `Sized`, or remove the `?Sized` bound",
154+
"",
155+
Applicability::MaybeIncorrect,
156+
);
157+
},
158+
);
159+
160+
return;
161+
}
162+
}
163+
}
164+
}

tests/ui-toml/type_repetition_in_bounds/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![allow(clippy::needless_maybe_sized)]
12
#![warn(clippy::type_repetition_in_bounds)]
23

34
fn f<T>()

tests/ui-toml/type_repetition_in_bounds/main.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this type has already been used as a bound predicate
2-
--> tests/ui-toml/type_repetition_in_bounds/main.rs:13:5
2+
--> tests/ui-toml/type_repetition_in_bounds/main.rs:14:5
33
|
44
LL | T: Unpin + PartialEq,
55
| ^^^^^^^^^^^^^^^^^^^^

tests/ui/auxiliary/proc_macros.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Gro
5858
const ESCAPE_CHAR: char = '$';
5959

6060
/// Takes a single token followed by a sequence of tokens. Returns the sequence of tokens with their
61-
/// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`.
61+
/// span set to that of the first token. Tokens may be escaped with either `$ident` or `$(tokens)`.
6262
#[proc_macro]
6363
pub fn with_span(input: TokenStream) -> TokenStream {
6464
let mut iter = input.into_iter();
@@ -72,7 +72,7 @@ pub fn with_span(input: TokenStream) -> TokenStream {
7272
}
7373

7474
/// Takes a sequence of tokens and return the tokens with the span set such that they appear to be
75-
/// from an external macro. Tokens may be escaped with either `#ident` or `#(tokens)`.
75+
/// from an external macro. Tokens may be escaped with either `$ident` or `$(tokens)`.
7676
#[proc_macro]
7777
pub fn external(input: TokenStream) -> TokenStream {
7878
let mut res = TokenStream::new();
@@ -84,7 +84,7 @@ pub fn external(input: TokenStream) -> TokenStream {
8484
}
8585

8686
/// Copies all the tokens, replacing all their spans with the given span. Tokens can be escaped
87-
/// either by `#ident` or `#(tokens)`.
87+
/// either by `$ident` or `$(tokens)`.
8888
fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Result<()> {
8989
while let Some(tt) = input.next() {
9090
match tt {

tests/ui/needless_maybe_sized.fixed

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//@aux-build:proc_macros.rs
2+
3+
#![allow(unused, clippy::multiple_bound_locations)]
4+
#![warn(clippy::needless_maybe_sized)]
5+
6+
extern crate proc_macros;
7+
use proc_macros::external;
8+
9+
fn directly<T: Sized>(t: &T) {}
10+
11+
trait A: Sized {}
12+
trait B: A {}
13+
14+
fn depth_1<T: A>(t: &T) {}
15+
fn depth_2<T: B>(t: &T) {}
16+
17+
// We only need to show one
18+
fn multiple_paths<T: A + B>(t: &T) {}
19+
20+
fn in_where<T>(t: &T)
21+
where
22+
T: Sized,
23+
{
24+
}
25+
26+
fn mixed_1<T: Sized>(t: &T)
27+
{
28+
}
29+
30+
fn mixed_2<T>(t: &T)
31+
where
32+
T: Sized,
33+
{
34+
}
35+
36+
fn mixed_3<T>(t: &T)
37+
where
38+
T: Sized,
39+
{
40+
}
41+
42+
struct Struct<T: Sized>(T);
43+
44+
impl<T: Sized> Struct<T> {
45+
fn method<U: Sized>(&self) {}
46+
}
47+
48+
enum Enum<T: Sized + 'static> {
49+
Variant(&'static T),
50+
}
51+
52+
union Union<'a, T: Sized> {
53+
a: &'a T,
54+
}
55+
56+
trait Trait<T: Sized> {
57+
fn trait_method<U: Sized>() {}
58+
59+
type GAT<U: Sized>;
60+
61+
type Assoc: Sized + ?Sized; // False negative
62+
}
63+
64+
trait SecondInTrait: Send + Sized {}
65+
fn second_in_trait<T: SecondInTrait>() {}
66+
67+
fn impl_trait(_: &(impl Sized)) {}
68+
69+
trait GenericTrait<T>: Sized {}
70+
fn in_generic_trait<T: GenericTrait<U>, U>() {}
71+
72+
mod larger_graph {
73+
// C1 C2 Sized
74+
// \ /\ /
75+
// B1 B2
76+
// \ /
77+
// A1
78+
79+
trait C1 {}
80+
trait C2 {}
81+
trait B1: C1 + C2 {}
82+
trait B2: C2 + Sized {}
83+
trait A1: B1 + B2 {}
84+
85+
fn larger_graph<T: A1>() {}
86+
}
87+
88+
// Should not lint
89+
90+
fn sized<T: Sized>() {}
91+
fn maybe_sized<T: ?Sized>() {}
92+
93+
struct SeparateBounds<T: ?Sized>(T);
94+
impl<T: Sized> SeparateBounds<T> {}
95+
96+
trait P {}
97+
trait Q: P {}
98+
99+
fn ok_depth_1<T: P + ?Sized>() {}
100+
fn ok_depth_2<T: Q + ?Sized>() {}
101+
102+
external! {
103+
fn in_macro<T: Clone + ?Sized>(t: &T) {}
104+
105+
fn with_local_clone<T: $Clone + ?Sized>(t: &T) {}
106+
}
107+
108+
#[derive(Clone)]
109+
struct InDerive<T: ?Sized> {
110+
t: T,
111+
}
112+
113+
struct Refined<T: ?Sized>(T);
114+
impl<T: Sized> Refined<T> {}
115+
116+
fn main() {}

0 commit comments

Comments
 (0)