Skip to content

Commit 3787a0c

Browse files
committed
Auto merge of #11350 - y21:issue11349, r=xFrednet
[`type_id_on_box`]: lint on any `Box<dyn _>` Closes #11349. It now not only lints when calling `.type_id()` on the type `Box<dyn Any>`, but also on any `Box<dyn Trait>` where `Trait` is a subtrait of `Any` changelog: FN: [`type_id_on_box`]: lint if `Any` is a sub trait
2 parents cebf879 + f6c0063 commit 3787a0c

7 files changed

+176
-41
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3002,13 +3002,22 @@ declare_clippy_lint! {
30023002

30033003
declare_clippy_lint! {
30043004
/// ### What it does
3005-
/// Looks for calls to `<Box<dyn Any> as Any>::type_id`.
3005+
/// Looks for calls to `.type_id()` on a `Box<dyn _>`.
30063006
///
30073007
/// ### Why is this bad?
3008-
/// This most certainly does not do what the user expects and is very easy to miss.
3009-
/// Calling `type_id` on a `Box<dyn Any>` calls `type_id` on the `Box<..>` itself,
3010-
/// so this will return the `TypeId` of the `Box<dyn Any>` type (not the type id
3011-
/// of the value referenced by the box!).
3008+
/// This almost certainly does not do what the user expects and can lead to subtle bugs.
3009+
/// Calling `.type_id()` on a `Box<dyn Trait>` returns a fixed `TypeId` of the `Box` itself,
3010+
/// rather than returning the `TypeId` of the underlying type behind the trait object.
3011+
///
3012+
/// For `Box<dyn Any>` specifically (and trait objects that have `Any` as its supertrait),
3013+
/// this lint will provide a suggestion, which is to dereference the receiver explicitly
3014+
/// to go from `Box<dyn Any>` to `dyn Any`.
3015+
/// This makes sure that `.type_id()` resolves to a dynamic call on the trait object
3016+
/// and not on the box.
3017+
///
3018+
/// If the fixed `TypeId` of the `Box` is the intended behavior, it's better to be explicit about it
3019+
/// and write `TypeId::of::<Box<dyn Trait>>()`:
3020+
/// this makes it clear that a fixed `TypeId` is returned and not the `TypeId` of the implementor.
30123021
///
30133022
/// ### Example
30143023
/// ```rust,ignore
@@ -3028,7 +3037,7 @@ declare_clippy_lint! {
30283037
#[clippy::version = "1.73.0"]
30293038
pub TYPE_ID_ON_BOX,
30303039
suspicious,
3031-
"calling `.type_id()` on `Box<dyn Any>`"
3040+
"calling `.type_id()` on a boxed trait object"
30323041
}
30333042

30343043
declare_clippy_lint! {

clippy_lints/src/methods/type_id_on_box.rs

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,33 @@ use rustc_errors::Applicability;
55
use rustc_hir::Expr;
66
use rustc_lint::LateContext;
77
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
8+
use rustc_middle::ty::print::with_forced_trimmed_paths;
89
use rustc_middle::ty::{self, ExistentialPredicate, Ty};
910
use rustc_span::{sym, Span};
1011

11-
fn is_dyn_any(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
12+
/// Checks if the given type is `dyn Any`, or a trait object that has `Any` as a supertrait.
13+
/// Only in those cases will its vtable have a `type_id` method that returns the implementor's
14+
/// `TypeId`, and only in those cases can we give a proper suggestion to dereference the box.
15+
///
16+
/// If this returns false, then `.type_id()` likely (this may have FNs) will not be what the user
17+
/// expects in any case and dereferencing it won't help either. It will likely require some
18+
/// other changes, but it is still worth emitting a lint.
19+
/// See <https://github.com/rust-lang/rust-clippy/pull/11350#discussion_r1544863005> for more details.
20+
fn is_subtrait_of_any(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
1221
if let ty::Dynamic(preds, ..) = ty.kind() {
1322
preds.iter().any(|p| match p.skip_binder() {
14-
ExistentialPredicate::Trait(tr) => cx.tcx.is_diagnostic_item(sym::Any, tr.def_id),
23+
ExistentialPredicate::Trait(tr) => {
24+
cx.tcx.is_diagnostic_item(sym::Any, tr.def_id)
25+
|| cx
26+
.tcx
27+
.super_predicates_of(tr.def_id)
28+
.predicates
29+
.iter()
30+
.any(|(clause, _)| {
31+
matches!(clause.kind().skip_binder(), ty::ClauseKind::Trait(super_tr)
32+
if cx.tcx.is_diagnostic_item(sym::Any, super_tr.def_id()))
33+
})
34+
},
1535
_ => false,
1636
})
1737
} else {
@@ -26,36 +46,42 @@ pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span)
2646
&& let ty::Ref(_, ty, _) = recv_ty.kind()
2747
&& let ty::Adt(adt, args) = ty.kind()
2848
&& adt.is_box()
29-
&& is_dyn_any(cx, args.type_at(0))
49+
&& let inner_box_ty = args.type_at(0)
50+
&& let ty::Dynamic(..) = inner_box_ty.kind()
3051
{
52+
let ty_name = with_forced_trimmed_paths!(ty.to_string());
53+
3154
span_lint_and_then(
3255
cx,
3356
TYPE_ID_ON_BOX,
3457
call_span,
35-
"calling `.type_id()` on a `Box<dyn Any>`",
58+
&format!("calling `.type_id()` on `{ty_name}`"),
3659
|diag| {
3760
let derefs = recv_adjusts
3861
.iter()
3962
.filter(|adj| matches!(adj.kind, Adjust::Deref(None)))
4063
.count();
4164

42-
let mut sugg = "*".repeat(derefs + 1);
43-
sugg += &snippet(cx, receiver.span, "<expr>");
44-
4565
diag.note(
46-
"this returns the type id of the literal type `Box<dyn Any>` instead of the \
66+
"this returns the type id of the literal type `Box<_>` instead of the \
4767
type id of the boxed value, which is most likely not what you want",
4868
)
49-
.note(
50-
"if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, \
51-
which makes it more clear",
52-
)
53-
.span_suggestion(
54-
receiver.span,
55-
"consider dereferencing first",
56-
format!("({sugg})"),
57-
Applicability::MaybeIncorrect,
58-
);
69+
.note(format!(
70+
"if this is intentional, use `TypeId::of::<{ty_name}>()` instead, \
71+
which makes it more clear"
72+
));
73+
74+
if is_subtrait_of_any(cx, inner_box_ty) {
75+
let mut sugg = "*".repeat(derefs + 1);
76+
sugg += &snippet(cx, receiver.span, "<expr>");
77+
78+
diag.span_suggestion(
79+
receiver.span,
80+
"consider dereferencing first",
81+
format!("({sugg})"),
82+
Applicability::MaybeIncorrect,
83+
);
84+
}
5985
},
6086
);
6187
}

tests/ui/type_id_on_box.fixed

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,37 @@ fn existential() -> impl Any {
1919
Box::new(1) as Box<dyn Any>
2020
}
2121

22+
trait AnySubTrait: Any {}
23+
impl<T: Any> AnySubTrait for T {}
24+
2225
fn main() {
26+
// Don't lint, calling `.type_id()` on a `&dyn Any` does the expected thing
27+
let ref_dyn: &dyn Any = &42;
28+
let _ = ref_dyn.type_id();
29+
2330
let any_box: Box<dyn Any> = Box::new(0usize);
2431
let _ = (*any_box).type_id();
25-
let _ = TypeId::of::<Box<dyn Any>>(); // Don't lint. We explicitly say "do this instead" if this is intentional
32+
//~^ ERROR: calling `.type_id()` on
33+
34+
// Don't lint. We explicitly say "do this instead" if this is intentional
35+
let _ = TypeId::of::<Box<dyn Any>>();
2636
let _ = (*any_box).type_id();
37+
38+
// 2 derefs are needed here to get to the `dyn Any`
2739
let any_box: &Box<dyn Any> = &(Box::new(0usize) as Box<dyn Any>);
28-
let _ = (**any_box).type_id(); // 2 derefs are needed here to get to the `dyn Any`
40+
let _ = (**any_box).type_id();
41+
//~^ ERROR: calling `.type_id()` on
2942

3043
let b = existential();
31-
let _ = b.type_id(); // Don't lint.
44+
let _ = b.type_id(); // Don't
45+
46+
let b: Box<dyn AnySubTrait> = Box::new(1);
47+
let _ = (*b).type_id();
48+
//~^ ERROR: calling `.type_id()` on
3249

3350
let b: SomeBox = Box::new(0usize);
3451
let _ = (*b).type_id();
52+
//~^ ERROR: calling `.type_id()` on
3553

3654
let b = BadBox(Box::new(0usize));
3755
let _ = b.type_id(); // Don't lint. This is a call to `<BadBox as Any>::type_id`. Not `std::boxed::Box`!

tests/ui/type_id_on_box.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,37 @@ fn existential() -> impl Any {
1919
Box::new(1) as Box<dyn Any>
2020
}
2121

22+
trait AnySubTrait: Any {}
23+
impl<T: Any> AnySubTrait for T {}
24+
2225
fn main() {
26+
// Don't lint, calling `.type_id()` on a `&dyn Any` does the expected thing
27+
let ref_dyn: &dyn Any = &42;
28+
let _ = ref_dyn.type_id();
29+
2330
let any_box: Box<dyn Any> = Box::new(0usize);
2431
let _ = any_box.type_id();
25-
let _ = TypeId::of::<Box<dyn Any>>(); // Don't lint. We explicitly say "do this instead" if this is intentional
32+
//~^ ERROR: calling `.type_id()` on
33+
34+
// Don't lint. We explicitly say "do this instead" if this is intentional
35+
let _ = TypeId::of::<Box<dyn Any>>();
2636
let _ = (*any_box).type_id();
37+
38+
// 2 derefs are needed here to get to the `dyn Any`
2739
let any_box: &Box<dyn Any> = &(Box::new(0usize) as Box<dyn Any>);
28-
let _ = any_box.type_id(); // 2 derefs are needed here to get to the `dyn Any`
40+
let _ = any_box.type_id();
41+
//~^ ERROR: calling `.type_id()` on
2942

3043
let b = existential();
31-
let _ = b.type_id(); // Don't lint.
44+
let _ = b.type_id(); // Don't
45+
46+
let b: Box<dyn AnySubTrait> = Box::new(1);
47+
let _ = b.type_id();
48+
//~^ ERROR: calling `.type_id()` on
3249

3350
let b: SomeBox = Box::new(0usize);
3451
let _ = b.type_id();
52+
//~^ ERROR: calling `.type_id()` on
3553

3654
let b = BadBox(Box::new(0usize));
3755
let _ = b.type_id(); // Don't lint. This is a call to `<BadBox as Any>::type_id`. Not `std::boxed::Box`!

tests/ui/type_id_on_box.stderr

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,48 @@
1-
error: calling `.type_id()` on a `Box<dyn Any>`
2-
--> tests/ui/type_id_on_box.rs:24:13
1+
error: calling `.type_id()` on `Box<dyn Any>`
2+
--> tests/ui/type_id_on_box.rs:31:13
33
|
44
LL | let _ = any_box.type_id();
55
| -------^^^^^^^^^^
66
| |
77
| help: consider dereferencing first: `(*any_box)`
88
|
9-
= note: this returns the type id of the literal type `Box<dyn Any>` instead of the type id of the boxed value, which is most likely not what you want
9+
= note: this returns the type id of the literal type `Box<_>` instead of the type id of the boxed value, which is most likely not what you want
1010
= note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
1111
= note: `-D clippy::type-id-on-box` implied by `-D warnings`
1212
= help: to override `-D warnings` add `#[allow(clippy::type_id_on_box)]`
1313

14-
error: calling `.type_id()` on a `Box<dyn Any>`
15-
--> tests/ui/type_id_on_box.rs:28:13
14+
error: calling `.type_id()` on `Box<dyn Any>`
15+
--> tests/ui/type_id_on_box.rs:40:13
1616
|
17-
LL | let _ = any_box.type_id(); // 2 derefs are needed here to get to the `dyn Any`
17+
LL | let _ = any_box.type_id();
1818
| -------^^^^^^^^^^
1919
| |
2020
| help: consider dereferencing first: `(**any_box)`
2121
|
22-
= note: this returns the type id of the literal type `Box<dyn Any>` instead of the type id of the boxed value, which is most likely not what you want
22+
= note: this returns the type id of the literal type `Box<_>` instead of the type id of the boxed value, which is most likely not what you want
2323
= note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
2424

25-
error: calling `.type_id()` on a `Box<dyn Any>`
26-
--> tests/ui/type_id_on_box.rs:34:13
25+
error: calling `.type_id()` on `Box<dyn AnySubTrait>`
26+
--> tests/ui/type_id_on_box.rs:47:13
27+
|
28+
LL | let _ = b.type_id();
29+
| -^^^^^^^^^^
30+
| |
31+
| help: consider dereferencing first: `(*b)`
32+
|
33+
= note: this returns the type id of the literal type `Box<_>` instead of the type id of the boxed value, which is most likely not what you want
34+
= note: if this is intentional, use `TypeId::of::<Box<dyn AnySubTrait>>()` instead, which makes it more clear
35+
36+
error: calling `.type_id()` on `Box<dyn Any>`
37+
--> tests/ui/type_id_on_box.rs:51:13
2738
|
2839
LL | let _ = b.type_id();
2940
| -^^^^^^^^^^
3041
| |
3142
| help: consider dereferencing first: `(*b)`
3243
|
33-
= note: this returns the type id of the literal type `Box<dyn Any>` instead of the type id of the boxed value, which is most likely not what you want
44+
= note: this returns the type id of the literal type `Box<_>` instead of the type id of the boxed value, which is most likely not what you want
3445
= note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
3546

36-
error: aborting due to 3 previous errors
47+
error: aborting due to 4 previous errors
3748

tests/ui/type_id_on_box_unfixable.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#![warn(clippy::type_id_on_box)]
2+
3+
use std::any::{Any, TypeId};
4+
use std::ops::Deref;
5+
6+
trait AnySubTrait: Any {}
7+
impl<T: Any> AnySubTrait for T {}
8+
9+
// `Any` is an indirect supertrait
10+
trait AnySubSubTrait: AnySubTrait {}
11+
impl<T: AnySubTrait> AnySubSubTrait for T {}
12+
13+
// This trait mentions `Any` in its predicates, but it is not a subtrait of `Any`.
14+
trait NormalTrait
15+
where
16+
i32: Any,
17+
{
18+
}
19+
impl<T> NormalTrait for T {}
20+
21+
fn main() {
22+
// (currently we don't look deeper than one level into the supertrait hierachy, but we probably
23+
// could)
24+
let b: Box<dyn AnySubSubTrait> = Box::new(1);
25+
let _ = b.type_id();
26+
//~^ ERROR: calling `.type_id()` on
27+
28+
let b: Box<dyn NormalTrait> = Box::new(1);
29+
let _ = b.type_id();
30+
//~^ ERROR: calling `.type_id()` on
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: calling `.type_id()` on `Box<dyn AnySubSubTrait>`
2+
--> tests/ui/type_id_on_box_unfixable.rs:25:13
3+
|
4+
LL | let _ = b.type_id();
5+
| ^^^^^^^^^^^
6+
|
7+
= note: this returns the type id of the literal type `Box<_>` instead of the type id of the boxed value, which is most likely not what you want
8+
= note: if this is intentional, use `TypeId::of::<Box<dyn AnySubSubTrait>>()` instead, which makes it more clear
9+
= note: `-D clippy::type-id-on-box` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::type_id_on_box)]`
11+
12+
error: calling `.type_id()` on `Box<dyn NormalTrait>`
13+
--> tests/ui/type_id_on_box_unfixable.rs:29:13
14+
|
15+
LL | let _ = b.type_id();
16+
| ^^^^^^^^^^^
17+
|
18+
= note: this returns the type id of the literal type `Box<_>` instead of the type id of the boxed value, which is most likely not what you want
19+
= note: if this is intentional, use `TypeId::of::<Box<dyn NormalTrait>>()` instead, which makes it more clear
20+
21+
error: aborting due to 2 previous errors
22+

0 commit comments

Comments
 (0)