Skip to content

Commit 36e4c20

Browse files
committed
lint on any Box<dyn _>, but provide a suggestion for subtypes of dyn Any
1 parent 37be3e4 commit 36e4c20

File tree

2 files changed

+55
-42
lines changed

2 files changed

+55
-42
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: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::borrow::Cow;
2-
31
use crate::methods::TYPE_ID_ON_BOX;
42
use clippy_utils::diagnostics::span_lint_and_then;
53
use clippy_utils::source::snippet;
@@ -11,33 +9,33 @@ use rustc_middle::ty::print::with_forced_trimmed_paths;
119
use rustc_middle::ty::{self, ExistentialPredicate, Ty};
1210
use rustc_span::{sym, Span};
1311

14-
/// Checks if a [`Ty`] is a `dyn Any` or a `dyn Trait` where `Trait: Any`
15-
/// and returns the name of the trait object.
16-
fn is_dyn_any(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Cow<'static, str>> {
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 {
1721
if let ty::Dynamic(preds, ..) = ty.kind() {
18-
preds.iter().find_map(|p| match p.skip_binder() {
22+
preds.iter().any(|p| match p.skip_binder() {
1923
ExistentialPredicate::Trait(tr) => {
20-
if cx.tcx.is_diagnostic_item(sym::Any, tr.def_id) {
21-
Some(Cow::Borrowed("Any"))
22-
} else if cx
23-
.tcx
24-
.super_predicates_of(tr.def_id)
25-
.predicates
26-
.iter()
27-
.any(|(clause, _)| {
28-
matches!(clause.kind().skip_binder(), ty::ClauseKind::Trait(super_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)
2932
if cx.tcx.is_diagnostic_item(sym::Any, super_tr.def_id()))
30-
})
31-
{
32-
Some(Cow::Owned(with_forced_trimmed_paths!(cx.tcx.def_path_str(tr.def_id))))
33-
} else {
34-
None
35-
}
33+
})
3634
},
37-
_ => None,
35+
_ => false,
3836
})
3937
} else {
40-
None
38+
false
4139
}
4240
}
4341

@@ -48,36 +46,42 @@ pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span)
4846
&& let ty::Ref(_, ty, _) = recv_ty.kind()
4947
&& let ty::Adt(adt, args) = ty.kind()
5048
&& adt.is_box()
51-
&& let Some(trait_path) = 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()
5251
{
52+
let ty_name = with_forced_trimmed_paths!(ty.to_string());
53+
5354
span_lint_and_then(
5455
cx,
5556
TYPE_ID_ON_BOX,
5657
call_span,
57-
&format!("calling `.type_id()` on `Box<dyn {trait_path}>`"),
58+
&format!("calling `.type_id()` on `{ty_name}`"),
5859
|diag| {
5960
let derefs = recv_adjusts
6061
.iter()
6162
.filter(|adj| matches!(adj.kind, Adjust::Deref(None)))
6263
.count();
6364

64-
let mut sugg = "*".repeat(derefs + 1);
65-
sugg += &snippet(cx, receiver.span, "<expr>");
66-
6765
diag.note(
6866
"this returns the type id of the literal type `Box<_>` instead of the \
6967
type id of the boxed value, which is most likely not what you want",
7068
)
7169
.note(format!(
72-
"if this is intentional, use `TypeId::of::<Box<dyn {trait_path}>>()` instead, \
70+
"if this is intentional, use `TypeId::of::<{ty_name}>()` instead, \
7371
which makes it more clear"
74-
))
75-
.span_suggestion(
76-
receiver.span,
77-
"consider dereferencing first",
78-
format!("({sugg})"),
79-
Applicability::MaybeIncorrect,
80-
);
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+
}
8185
},
8286
);
8387
}

0 commit comments

Comments
 (0)