Skip to content

Commit 18d86b3

Browse files
committed
Introduce coerce_any_ref_to_any
1 parent 40bead0 commit 18d86b3

7 files changed

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

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)