Skip to content

Commit c46ddeb

Browse files
committed
Auto merge of #10987 - y21:type_id_on_box, r=llogiq
new lint: `type_id_on_box` Closes #7687. A new lint that detects calling `.type_id()` on `Box<dyn Any>` (and not on the underlying `dyn Any`), which can make up for some pretty confusing bugs! changelog: new lint: [`type_id_on_box`]
2 parents 4752466 + 1b6738b commit c46ddeb

File tree

7 files changed

+218
-0
lines changed

7 files changed

+218
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5266,6 +5266,7 @@ Released 2018-09-13
52665266
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
52675267
[`tuple_array_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions
52685268
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
5269+
[`type_id_on_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_id_on_box
52695270
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
52705271
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
52715272
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
404404
crate::methods::SUSPICIOUS_MAP_INFO,
405405
crate::methods::SUSPICIOUS_SPLITN_INFO,
406406
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
407+
crate::methods::TYPE_ID_ON_BOX_INFO,
407408
crate::methods::UNINIT_ASSUMED_INIT_INFO,
408409
crate::methods::UNIT_HASH_INFO,
409410
crate::methods::UNNECESSARY_FILTER_MAP_INFO,

clippy_lints/src/methods/mod.rs

+36
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ mod suspicious_command_arg_space;
8888
mod suspicious_map;
8989
mod suspicious_splitn;
9090
mod suspicious_to_owned;
91+
mod type_id_on_box;
9192
mod uninit_assumed_init;
9293
mod unit_hash;
9394
mod unnecessary_filter_map;
@@ -2925,6 +2926,37 @@ declare_clippy_lint! {
29252926
"use of sort() when sort_unstable() is equivalent"
29262927
}
29272928

2929+
declare_clippy_lint! {
2930+
/// ### What it does
2931+
/// Looks for calls to `<Box<dyn Any> as Any>::type_id`.
2932+
///
2933+
/// ### Why is this bad?
2934+
/// This most certainly does not do what the user expects and is very easy to miss.
2935+
/// Calling `type_id` on a `Box<dyn Any>` calls `type_id` on the `Box<..>` itself,
2936+
/// so this will return the `TypeId` of the `Box<dyn Any>` type (not the type id
2937+
/// of the value referenced by the box!).
2938+
///
2939+
/// ### Example
2940+
/// ```rust,ignore
2941+
/// use std::any::{Any, TypeId};
2942+
///
2943+
/// let any_box: Box<dyn Any> = Box::new(42_i32);
2944+
/// assert_eq!(any_box.type_id(), TypeId::of::<i32>()); // ⚠️ this fails!
2945+
/// ```
2946+
/// Use instead:
2947+
/// ```rust
2948+
/// use std::any::{Any, TypeId};
2949+
///
2950+
/// let any_box: Box<dyn Any> = Box::new(42_i32);
2951+
/// assert_eq!((*any_box).type_id(), TypeId::of::<i32>());
2952+
/// // ^ dereference first, to call `type_id` on `dyn Any`
2953+
/// ```
2954+
#[clippy::version = "1.72.0"]
2955+
pub TYPE_ID_ON_BOX,
2956+
suspicious,
2957+
"calling `.type_id()` on `Box<dyn Any>`"
2958+
}
2959+
29282960
declare_clippy_lint! {
29292961
/// ### What it does
29302962
/// Detects `().hash(_)`.
@@ -3389,6 +3421,7 @@ impl_lint_pass!(Methods => [
33893421
STRING_EXTEND_CHARS,
33903422
ITER_CLONED_COLLECT,
33913423
ITER_WITH_DRAIN,
3424+
TYPE_ID_ON_BOX,
33923425
USELESS_ASREF,
33933426
UNNECESSARY_FOLD,
33943427
UNNECESSARY_FILTER_MAP,
@@ -3914,6 +3947,9 @@ impl Methods {
39143947
("to_os_string" | "to_path_buf" | "to_vec", []) => {
39153948
implicit_clone::check(cx, name, expr, recv);
39163949
},
3950+
("type_id", []) => {
3951+
type_id_on_box::check(cx, recv, expr.span);
3952+
}
39173953
("unwrap", []) => {
39183954
match method_call(recv) {
39193955
Some(("get", recv, [get_arg], _, _)) => {
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use crate::methods::TYPE_ID_ON_BOX;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::source::snippet;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::Expr;
6+
use rustc_lint::LateContext;
7+
use rustc_middle::ty::adjustment::Adjust;
8+
use rustc_middle::ty::adjustment::Adjustment;
9+
use rustc_middle::ty::Ty;
10+
use rustc_middle::ty::{self, ExistentialPredicate};
11+
use rustc_span::{sym, Span};
12+
13+
fn is_dyn_any(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
14+
if let ty::Dynamic(preds, ..) = ty.kind() {
15+
preds.iter().any(|p| match p.skip_binder() {
16+
ExistentialPredicate::Trait(tr) => cx.tcx.is_diagnostic_item(sym::Any, tr.def_id),
17+
_ => false,
18+
})
19+
} else {
20+
false
21+
}
22+
}
23+
24+
pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span) {
25+
let recv_adjusts = cx.typeck_results().expr_adjustments(receiver);
26+
27+
if let Some(Adjustment { target: recv_ty, .. }) = recv_adjusts.last()
28+
&& let ty::Ref(_, ty, _) = recv_ty.kind()
29+
&& let ty::Adt(adt, substs) = ty.kind()
30+
&& adt.is_box()
31+
&& is_dyn_any(cx, substs.type_at(0))
32+
{
33+
span_lint_and_then(
34+
cx,
35+
TYPE_ID_ON_BOX,
36+
call_span,
37+
"calling `.type_id()` on a `Box<dyn Any>`",
38+
|diag| {
39+
let derefs = recv_adjusts
40+
.iter()
41+
.filter(|adj| matches!(adj.kind, Adjust::Deref(None)))
42+
.count();
43+
44+
let mut sugg = "*".repeat(derefs + 1);
45+
sugg += &snippet(cx, receiver.span, "<expr>");
46+
47+
diag.note(
48+
"this returns the type id of the literal type `Box<dyn Any>` instead of the \
49+
type id of the boxed value, which is most likely not what you want"
50+
)
51+
.note(
52+
"if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, \
53+
which makes it more clear"
54+
)
55+
.span_suggestion(
56+
receiver.span,
57+
"consider dereferencing first",
58+
format!("({sugg})"),
59+
Applicability::MaybeIncorrect,
60+
);
61+
},
62+
);
63+
}
64+
}

tests/ui/type_id_on_box.fixed

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@run-rustfix
2+
3+
#![warn(clippy::type_id_on_box)]
4+
5+
use std::any::{Any, TypeId};
6+
use std::ops::Deref;
7+
8+
type SomeBox = Box<dyn Any>;
9+
10+
struct BadBox(Box<dyn Any>);
11+
12+
impl Deref for BadBox {
13+
type Target = Box<dyn Any>;
14+
15+
fn deref(&self) -> &Self::Target {
16+
&self.0
17+
}
18+
}
19+
20+
fn existential() -> impl Any {
21+
Box::new(1) as Box<dyn Any>
22+
}
23+
24+
fn main() {
25+
let any_box: Box<dyn Any> = Box::new(0usize);
26+
let _ = (*any_box).type_id();
27+
let _ = TypeId::of::<Box<dyn Any>>(); // Don't lint. We explicitly say "do this instead" if this is intentional
28+
let _ = (*any_box).type_id();
29+
let any_box: &Box<dyn Any> = &(Box::new(0usize) as Box<dyn Any>);
30+
let _ = (**any_box).type_id(); // 2 derefs are needed here to get to the `dyn Any`
31+
32+
let b = existential();
33+
let _ = b.type_id(); // Don't lint.
34+
35+
let b: SomeBox = Box::new(0usize);
36+
let _ = (*b).type_id();
37+
38+
let b = BadBox(Box::new(0usize));
39+
let _ = b.type_id(); // Don't lint. This is a call to `<BadBox as Any>::type_id`. Not `std::boxed::Box`!
40+
}

tests/ui/type_id_on_box.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@run-rustfix
2+
3+
#![warn(clippy::type_id_on_box)]
4+
5+
use std::any::{Any, TypeId};
6+
use std::ops::Deref;
7+
8+
type SomeBox = Box<dyn Any>;
9+
10+
struct BadBox(Box<dyn Any>);
11+
12+
impl Deref for BadBox {
13+
type Target = Box<dyn Any>;
14+
15+
fn deref(&self) -> &Self::Target {
16+
&self.0
17+
}
18+
}
19+
20+
fn existential() -> impl Any {
21+
Box::new(1) as Box<dyn Any>
22+
}
23+
24+
fn main() {
25+
let any_box: Box<dyn Any> = Box::new(0usize);
26+
let _ = any_box.type_id();
27+
let _ = TypeId::of::<Box<dyn Any>>(); // Don't lint. We explicitly say "do this instead" if this is intentional
28+
let _ = (*any_box).type_id();
29+
let any_box: &Box<dyn Any> = &(Box::new(0usize) as Box<dyn Any>);
30+
let _ = any_box.type_id(); // 2 derefs are needed here to get to the `dyn Any`
31+
32+
let b = existential();
33+
let _ = b.type_id(); // Don't lint.
34+
35+
let b: SomeBox = Box::new(0usize);
36+
let _ = b.type_id();
37+
38+
let b = BadBox(Box::new(0usize));
39+
let _ = b.type_id(); // Don't lint. This is a call to `<BadBox as Any>::type_id`. Not `std::boxed::Box`!
40+
}

tests/ui/type_id_on_box.stderr

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error: calling `.type_id()` on a `Box<dyn Any>`
2+
--> $DIR/type_id_on_box.rs:26:13
3+
|
4+
LL | let _ = any_box.type_id();
5+
| -------^^^^^^^^^^
6+
| |
7+
| help: consider dereferencing first: `(*any_box)`
8+
|
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
10+
= note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
11+
= note: `-D clippy::type-id-on-box` implied by `-D warnings`
12+
13+
error: calling `.type_id()` on a `Box<dyn Any>`
14+
--> $DIR/type_id_on_box.rs:30:13
15+
|
16+
LL | let _ = any_box.type_id(); // 2 derefs are needed here to get to the `dyn Any`
17+
| -------^^^^^^^^^^
18+
| |
19+
| help: consider dereferencing first: `(**any_box)`
20+
|
21+
= 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: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
23+
24+
error: calling `.type_id()` on a `Box<dyn Any>`
25+
--> $DIR/type_id_on_box.rs:36:13
26+
|
27+
LL | let _ = b.type_id();
28+
| -^^^^^^^^^^
29+
| |
30+
| help: consider dereferencing first: `(*b)`
31+
|
32+
= 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
33+
= note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
34+
35+
error: aborting due to 3 previous errors
36+

0 commit comments

Comments
 (0)