Skip to content

Commit 2363e9d

Browse files
committed
Introduce coerce_any_ref_to_any
1 parent 40bead0 commit 2363e9d

7 files changed

+148
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5685,6 +5685,7 @@ Released 2018-09-13
56855685
[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
56865686
[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
56875687
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
5688+
[`coerce_any_ref_to_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#coerce_any_ref_to_any
56885689
[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
56895690
[`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if
56905691
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::sym;
3+
use rustc_hir::Expr;
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_middle::ty::{self, ExistentialPredicate, Ty};
6+
use rustc_session::declare_lint_pass;
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
///
11+
/// Detects cases where a `&dyn Any` is constructed directly referencing a `Box<dyn Any>` or
12+
/// other value that dereferences to `dyn Any`.
13+
///
14+
/// ### Why is this bad?
15+
///
16+
/// The intention is usually to borrow the `dyn Any` stored inside the value, rather than the
17+
/// value itself.
18+
///
19+
/// ### Example
20+
/// ```no_run
21+
/// let x: Box<dyn Any> = Box::new(());
22+
/// let _: &dyn Any = &x;
23+
/// ```
24+
/// Use instead:
25+
/// ```no_run
26+
/// let x: Box<dyn Any> = Box::new(());
27+
/// let _: &dyn Any = &**x;
28+
/// ```
29+
#[clippy::version = "1.88.0"]
30+
pub COERCE_ANY_REF_TO_ANY,
31+
nursery,
32+
"coercing reference to values containing `dyn Any` to `&dyn Any` is usually not intended"
33+
}
34+
declare_lint_pass!(CoerceAnyRefToAny => [COERCE_ANY_REF_TO_ANY]);
35+
36+
impl<'tcx> LateLintPass<'tcx> for CoerceAnyRefToAny {
37+
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
38+
// If this expression has an effective type of `&dyn Any` ...
39+
{
40+
let coerced_ty = cx.typeck_results().expr_ty_adjusted(e);
41+
42+
let ty::Ref(_, coerced_ref_ty, _) = *coerced_ty.kind() else {
43+
return;
44+
};
45+
if !is_dyn_any(cx, coerced_ref_ty) {
46+
return;
47+
}
48+
}
49+
50+
let expr_ty = cx.typeck_results().expr_ty(e);
51+
let ty::Ref(_, expr_ref_ty, _) = *expr_ty.kind() else {
52+
return;
53+
};
54+
// ... but only due to coercion ...
55+
if is_dyn_any(cx, expr_ref_ty) {
56+
return;
57+
}
58+
// ... and it also *derefs* to `dyn Any` ...
59+
let Some(target) = clippy_utils::ty::deref_chain(cx, expr_ty).skip(1).last() else {
60+
return;
61+
};
62+
if !is_dyn_any(cx, target) {
63+
return;
64+
}
65+
66+
// ... that's probably not intended.
67+
span_lint(
68+
cx,
69+
COERCE_ANY_REF_TO_ANY,
70+
e.span,
71+
format!(
72+
"coercing `{}` to `&dyn Any` rather than dereferencing the dyn Any inside it",
73+
expr_ty
74+
),
75+
);
76+
}
77+
}
78+
79+
fn is_dyn_any(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
80+
let ty::Dynamic(traits, ..) = ty.kind() else {
81+
return false;
82+
};
83+
if traits.iter().all(|binder| {
84+
let Some(predicate) = binder.no_bound_vars() else {
85+
return false;
86+
};
87+
let ExistentialPredicate::Trait(t) = predicate else {
88+
return false;
89+
};
90+
!cx.tcx.is_diagnostic_item(sym::Any, t.def_id)
91+
}) {
92+
return false;
93+
}
94+
true
95+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
7777
crate::cfg_not_test::CFG_NOT_TEST_INFO,
7878
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
7979
crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO,
80+
crate::coerce_any_ref_to_any::COERCE_ANY_REF_TO_ANY_INFO,
8081
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
8182
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
8283
crate::collapsible_if::COLLAPSIBLE_IF_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ mod casts;
9595
mod cfg_not_test;
9696
mod checked_conversions;
9797
mod cloned_ref_to_slice_refs;
98+
mod coerce_any_ref_to_any;
9899
mod cognitive_complexity;
99100
mod collapsible_if;
100101
mod collection_is_never_read;
@@ -944,5 +945,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
944945
store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap));
945946
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
946947
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
948+
store.register_late_pass(|_| Box::new(coerce_any_ref_to_any::CoerceAnyRefToAny));
947949
// add lints here, do not remove this comment, it's used in `new_lint`
948950
}

rust-toolchain.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
# begin autogenerated nightly
33
channel = "nightly-2025-05-01"
44
# end autogenerated nightly
5-
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
5+
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt", "rust-analyzer"]
66
profile = "minimal"

tests/ui/coerce_any_ref_to_any.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![warn(clippy::coerce_any_ref_to_any)]
2+
3+
use std::any::Any;
4+
5+
fn main() {
6+
let x: Box<dyn Any> = Box::new(());
7+
let ref_x = &x;
8+
9+
f(&x);
10+
//~^ coerce_any_ref_to_any
11+
12+
f(ref_x);
13+
//~^ coerce_any_ref_to_any
14+
15+
let _: &dyn Any = &x;
16+
//~^ coerce_any_ref_to_any
17+
18+
f(&42);
19+
f(&Box::new(()));
20+
f(&**ref_x);
21+
f(&*x);
22+
let _: &dyn Any = &*x;
23+
}
24+
25+
fn f(_: &dyn Any) {}

tests/ui/coerce_any_ref_to_any.stderr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: coercing `&std::boxed::Box<dyn std::any::Any>` to `&dyn Any` rather than dereferencing the dyn Any inside it
2+
--> tests/ui/coerce_any_ref_to_any.rs:9:7
3+
|
4+
LL | f(&x);
5+
| ^^
6+
|
7+
= note: `-D clippy::coerce-any-ref-to-any` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::coerce_any_ref_to_any)]`
9+
10+
error: coercing `&std::boxed::Box<dyn std::any::Any>` to `&dyn Any` rather than dereferencing the dyn Any inside it
11+
--> tests/ui/coerce_any_ref_to_any.rs:12:7
12+
|
13+
LL | f(ref_x);
14+
| ^^^^^
15+
16+
error: coercing `&std::boxed::Box<dyn std::any::Any>` to `&dyn Any` rather than dereferencing the dyn Any inside it
17+
--> tests/ui/coerce_any_ref_to_any.rs:15:23
18+
|
19+
LL | let _: &dyn Any = &x;
20+
| ^^
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)