about summary refs log tree commit diff
diff options
context:
space:
mode:
authory21 <30553356+y21@users.noreply.github.com>2023-06-18 22:20:10 +0200
committery21 <30553356+y21@users.noreply.github.com>2023-06-18 22:20:10 +0200
commitc5a9adc2bef3afcaf8bcdb260e218a82911a034e (patch)
tree333e3996cc064c45b5debdbc1ef835f4edda8026
parent3217f8aeaa754dde3baa078985518a219b18d04d (diff)
downloadrust-c5a9adc2bef3afcaf8bcdb260e218a82911a034e.tar.gz
rust-c5a9adc2bef3afcaf8bcdb260e218a82911a034e.zip
new lint: `type_id_on_box`
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/methods/mod.rs35
-rw-r--r--clippy_lints/src/methods/type_id_on_box.rs64
-rw-r--r--tests/ui/type_id_on_box.fixed20
-rw-r--r--tests/ui/type_id_on_box.rs20
-rw-r--r--tests/ui/type_id_on_box.stderr25
7 files changed, 166 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 269be545b0a..f82a3739259 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5230,6 +5230,7 @@ Released 2018-09-13
 [`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
 [`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
 [`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
+[`type_id_on_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_id_on_box
 [`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
 [`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
 [`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 7690e8f7247..0a2db21ca95 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -403,6 +403,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::methods::SUSPICIOUS_MAP_INFO,
     crate::methods::SUSPICIOUS_SPLITN_INFO,
     crate::methods::SUSPICIOUS_TO_OWNED_INFO,
+    crate::methods::TYPE_ID_ON_BOX_INFO,
     crate::methods::UNINIT_ASSUMED_INIT_INFO,
     crate::methods::UNIT_HASH_INFO,
     crate::methods::UNNECESSARY_FILTER_MAP_INFO,
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 99c984ba65a..e98fe99eac7 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -87,6 +87,7 @@ mod suspicious_command_arg_space;
 mod suspicious_map;
 mod suspicious_splitn;
 mod suspicious_to_owned;
+mod type_id_on_box;
 mod uninit_assumed_init;
 mod unit_hash;
 mod unnecessary_filter_map;
@@ -2924,6 +2925,37 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
+    /// Looks for calls to `<Box<dyn Any> as Any>::type_id`.
+    ///
+    /// ### Why is this bad?
+    /// This most certainly does not do what the user expects and is very easy to miss.
+    /// Calling `type_id` on a `Box<dyn Any>` calls `type_id` on the `Box<..>` itself,
+    /// so this will return the `TypeId` of the `Box<dyn Any>` type (not the type id
+    /// of the value referenced by the box!).
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// use std::any::{Any, TypeId};
+    ///
+    /// let any_box: Box<dyn Any> = Box::new(42_i32);
+    /// assert_eq!(any_box.type_id(), TypeId::of::<i32>()); // ⚠️ this fails!
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// use std::any::{Any, TypeId};
+    ///
+    /// let any_box: Box<dyn Any> = Box::new(42_i32);
+    /// assert_eq!((*any_box).type_id(), TypeId::of::<i32>());
+    /// //          ^ dereference first, to call `type_id` on `dyn Any`
+    /// ```
+    #[clippy::version = "1.47.0"]
+    pub TYPE_ID_ON_BOX,
+    suspicious,
+    "calling `.type_id()` on `Box<dyn Any>`"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
     /// Detects `().hash(_)`.
     ///
     /// ### Why is this bad?
@@ -3878,6 +3910,9 @@ impl Methods {
                 ("to_os_string" | "to_path_buf" | "to_vec", []) => {
                     implicit_clone::check(cx, name, expr, recv);
                 },
+                ("type_id", []) => {
+                    type_id_on_box::check(cx, recv, expr.span);
+                }
                 ("unwrap", []) => {
                     match method_call(recv) {
                         Some(("get", recv, [get_arg], _, _)) => {
diff --git a/clippy_lints/src/methods/type_id_on_box.rs b/clippy_lints/src/methods/type_id_on_box.rs
new file mode 100644
index 00000000000..8ccbd0d8d1b
--- /dev/null
+++ b/clippy_lints/src/methods/type_id_on_box.rs
@@ -0,0 +1,64 @@
+use crate::methods::TYPE_ID_ON_BOX;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::adjustment::Adjustment;
+use rustc_middle::ty::Ty;
+use rustc_middle::ty::{self, ExistentialPredicate};
+use rustc_span::{sym, Span};
+
+fn is_dyn_trait(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+    if let ty::Dynamic(preds, ..) = ty.kind() {
+        preds.iter().any(|p| match p.skip_binder() {
+            ExistentialPredicate::Trait(tr) => cx.tcx.is_diagnostic_item(sym::Any, tr.def_id),
+            _ => false,
+        })
+    } else {
+        false
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span) {
+    let recv_adjusts = cx.typeck_results().expr_adjustments(receiver);
+
+    if let Some(Adjustment { target: recv_ty, .. }) = recv_adjusts.last()
+        && let ty::Ref(_, ty, _) = recv_ty.kind()
+        && let ty::Adt(adt, substs) = ty.kind()
+        && adt.is_box()
+        && is_dyn_trait(cx, substs.type_at(0))
+    {
+        span_lint_and_then(
+            cx,
+            TYPE_ID_ON_BOX,
+            call_span,
+            "calling `.type_id()` on a `Box<dyn Any>`",
+            |diag| {
+                let derefs = recv_adjusts
+                    .iter()
+                    .filter(|adj| matches!(adj.kind, Adjust::Deref(None)))
+                    .count();
+
+                let mut sugg = "*".repeat(derefs + 1);
+                sugg += &snippet(cx, receiver.span, "<expr>");
+
+                diag.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"
+                )
+                .note(
+                    "if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, \
+                    which makes it more clear"
+                )
+                .span_suggestion(
+                    receiver.span,
+                    "consider dereferencing first",
+                    format!("({sugg})"),
+                    Applicability::MaybeIncorrect,
+                );
+            },
+        );
+    }
+}
diff --git a/tests/ui/type_id_on_box.fixed b/tests/ui/type_id_on_box.fixed
new file mode 100644
index 00000000000..fc177afacd4
--- /dev/null
+++ b/tests/ui/type_id_on_box.fixed
@@ -0,0 +1,20 @@
+//@run-rustfix
+
+#![warn(clippy::type_id_on_box)]
+
+use std::any::{Any, TypeId};
+
+fn existential() -> impl Any {
+    Box::new(1) as Box<dyn Any>
+}
+
+fn main() {
+    let any_box: Box<dyn Any> = Box::new(0usize);
+    let _ = (*any_box).type_id();
+    let _ = TypeId::of::<Box<dyn Any>>(); // don't lint, user probably explicitly wants to do this
+    let _ = (*any_box).type_id();
+    let any_box: &Box<dyn Any> = &(Box::new(0usize) as Box<dyn Any>);
+    let _ = (**any_box).type_id(); // 2 derefs are needed here
+    let b = existential();
+    let _ = b.type_id(); // don't lint
+}
diff --git a/tests/ui/type_id_on_box.rs b/tests/ui/type_id_on_box.rs
new file mode 100644
index 00000000000..36cf631a092
--- /dev/null
+++ b/tests/ui/type_id_on_box.rs
@@ -0,0 +1,20 @@
+//@run-rustfix
+
+#![warn(clippy::type_id_on_box)]
+
+use std::any::{Any, TypeId};
+
+fn existential() -> impl Any {
+    Box::new(1) as Box<dyn Any>
+}
+
+fn main() {
+    let any_box: Box<dyn Any> = Box::new(0usize);
+    let _ = any_box.type_id();
+    let _ = TypeId::of::<Box<dyn Any>>(); // don't lint, user probably explicitly wants to do this
+    let _ = (*any_box).type_id();
+    let any_box: &Box<dyn Any> = &(Box::new(0usize) as Box<dyn Any>);
+    let _ = any_box.type_id(); // 2 derefs are needed here
+    let b = existential();
+    let _ = b.type_id(); // don't lint
+}
diff --git a/tests/ui/type_id_on_box.stderr b/tests/ui/type_id_on_box.stderr
new file mode 100644
index 00000000000..47e0e814daf
--- /dev/null
+++ b/tests/ui/type_id_on_box.stderr
@@ -0,0 +1,25 @@
+error: calling `.type_id()` on a `Box<dyn Any>`
+  --> $DIR/type_id_on_box.rs:13:13
+   |
+LL |     let _ = any_box.type_id();
+   |             -------^^^^^^^^^^
+   |             |
+   |             help: consider dereferencing first: `(*any_box)`
+   |
+   = 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
+   = note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
+   = note: `-D clippy::type-id-on-box` implied by `-D warnings`
+
+error: calling `.type_id()` on a `Box<dyn Any>`
+  --> $DIR/type_id_on_box.rs:17:13
+   |
+LL |     let _ = any_box.type_id(); // 2 derefs are needed here
+   |             -------^^^^^^^^^^
+   |             |
+   |             help: consider dereferencing first: `(**any_box)`
+   |
+   = 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
+   = note: if this is intentional, use `TypeId::of::<Box<dyn Any>>()` instead, which makes it more clear
+
+error: aborting due to 2 previous errors
+