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}