rustc_lint/deref_into_dyn_supertrait.rs
1use rustc_hir::{self as hir, LangItem};
2use rustc_middle::ty;
3use rustc_session::{declare_lint, declare_lint_pass};
4use rustc_span::sym;
5use rustc_trait_selection::traits::supertraits;
6
7use crate::lints::{SupertraitAsDerefTarget, SupertraitAsDerefTargetLabel};
8use crate::{LateContext, LateLintPass, LintContext};
9
10declare_lint! {
11 /// The `deref_into_dyn_supertrait` lint is emitted whenever there is a `Deref` implementation
12 /// for `dyn SubTrait` with a `dyn SuperTrait` type as the `Output` type.
13 ///
14 /// These implementations are "shadowed" by trait upcasting (stabilized since
15 /// 1.86.0). The `deref` functions is no longer called implicitly, which might
16 /// change behavior compared to previous rustc versions.
17 ///
18 /// ### Example
19 ///
20 /// ```rust,compile_fail
21 /// #![deny(deref_into_dyn_supertrait)]
22 /// #![allow(dead_code)]
23 ///
24 /// use core::ops::Deref;
25 ///
26 /// trait A {}
27 /// trait B: A {}
28 /// impl<'a> Deref for dyn 'a + B {
29 /// type Target = dyn A;
30 /// fn deref(&self) -> &Self::Target {
31 /// todo!()
32 /// }
33 /// }
34 ///
35 /// fn take_a(_: &dyn A) { }
36 ///
37 /// fn take_b(b: &dyn B) {
38 /// take_a(b);
39 /// }
40 /// ```
41 ///
42 /// {{produces}}
43 ///
44 /// ### Explanation
45 ///
46 /// The trait upcasting coercion added a new coercion rule, taking priority over certain other
47 /// coercion rules, which causes some behavior change compared to older `rustc` versions.
48 ///
49 /// `deref` can be still called explicitly, it just isn't called as part of a deref coercion
50 /// (since trait upcasting coercion takes priority).
51 pub DEREF_INTO_DYN_SUPERTRAIT,
52 Allow,
53 "`Deref` implementation with a supertrait trait object for output is shadowed by trait upcasting",
54}
55
56declare_lint_pass!(DerefIntoDynSupertrait => [DEREF_INTO_DYN_SUPERTRAIT]);
57
58impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait {
59 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
60 let tcx = cx.tcx;
61 // `Deref` is being implemented for `t`
62 if let hir::ItemKind::Impl(impl_) = item.kind
63 // the trait is a `Deref` implementation
64 && let Some(trait_) = &impl_.of_trait
65 && let Some(did) = trait_.trait_def_id()
66 && tcx.is_lang_item(did, LangItem::Deref)
67 // the self type is `dyn t_principal`
68 && let self_ty = tcx.type_of(item.owner_id).instantiate_identity()
69 && let ty::Dynamic(data, _, ty::Dyn) = self_ty.kind()
70 && let Some(self_principal) = data.principal()
71 // `<T as Deref>::Target` is `dyn target_principal`
72 && let Some(target) = cx.get_associated_type(self_ty, did, "Target")
73 && let ty::Dynamic(data, _, ty::Dyn) = target.kind()
74 && let Some(target_principal) = data.principal()
75 // `target_principal` is a supertrait of `t_principal`
76 && let Some(supertrait_principal) = supertraits(tcx, self_principal.with_self_ty(tcx, self_ty))
77 .find(|supertrait| supertrait.def_id() == target_principal.def_id())
78 {
79 // erase regions in self type for better diagnostic presentation
80 let (self_ty, target_principal, supertrait_principal) =
81 tcx.erase_regions((self_ty, target_principal, supertrait_principal));
82 let label2 = impl_
83 .items
84 .iter()
85 .find_map(|i| (i.ident.name == sym::Target).then_some(i.span))
86 .map(|label| SupertraitAsDerefTargetLabel { label });
87 let span = tcx.def_span(item.owner_id.def_id);
88 cx.emit_span_lint(
89 DEREF_INTO_DYN_SUPERTRAIT,
90 span,
91 SupertraitAsDerefTarget {
92 self_ty,
93 supertrait_principal: supertrait_principal.map_bound(|trait_ref| {
94 ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
95 }),
96 target_principal,
97 label: span,
98 label2,
99 },
100 );
101 }
102 }
103}