use rustc_hir::{self as hir, LangItem}; use rustc_middle::ty; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::{Ident, sym}; use rustc_trait_selection::traits::supertraits; use crate::lints::{SupertraitAsDerefTarget, SupertraitAsDerefTargetLabel}; use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { /// The `deref_into_dyn_supertrait` lint is emitted whenever there is a `Deref` implementation /// for `dyn SubTrait` with a `dyn SuperTrait` type as the `Output` type. /// /// These implementations are "shadowed" by trait upcasting (stabilized since /// 1.86.0). The `deref` functions is no longer called implicitly, which might /// change behavior compared to previous rustc versions. /// /// ### Example /// /// ```rust,compile_fail /// #![deny(deref_into_dyn_supertrait)] /// #![allow(dead_code)] /// /// use core::ops::Deref; /// /// trait A {} /// trait B: A {} /// impl<'a> Deref for dyn 'a + B { /// type Target = dyn A; /// fn deref(&self) -> &Self::Target { /// todo!() /// } /// } /// /// fn take_a(_: &dyn A) { } /// /// fn take_b(b: &dyn B) { /// take_a(b); /// } /// ``` /// /// {{produces}} /// /// ### Explanation /// /// The trait upcasting coercion added a new coercion rule, taking priority over certain other /// coercion rules, which causes some behavior change compared to older `rustc` versions. /// /// `deref` can be still called explicitly, it just isn't called as part of a deref coercion /// (since trait upcasting coercion takes priority). pub DEREF_INTO_DYN_SUPERTRAIT, Allow, "`Deref` implementation with a supertrait trait object for output is shadowed by trait upcasting", } declare_lint_pass!(DerefIntoDynSupertrait => [DEREF_INTO_DYN_SUPERTRAIT]); impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { let tcx = cx.tcx; // `Deref` is being implemented for `t` if let hir::ItemKind::Impl(impl_) = item.kind // the trait is a `Deref` implementation && let Some(of_trait) = &impl_.of_trait && let Some(did) = of_trait.trait_ref.trait_def_id() && tcx.is_lang_item(did, LangItem::Deref) // the self type is `dyn t_principal` && let self_ty = tcx.type_of(item.owner_id).instantiate_identity() && let ty::Dynamic(data, _, ty::Dyn) = self_ty.kind() && let Some(self_principal) = data.principal() // `::Target` is `dyn target_principal` && let Some(target) = cx.get_associated_type(self_ty, did, sym::Target) && let ty::Dynamic(data, _, ty::Dyn) = target.kind() && let Some(target_principal) = data.principal() // `target_principal` is a supertrait of `t_principal` && let Some(supertrait_principal) = supertraits(tcx, self_principal.with_self_ty(tcx, self_ty)) .find(|supertrait| supertrait.def_id() == target_principal.def_id()) { // erase regions in self type for better diagnostic presentation let (self_ty, target_principal, supertrait_principal) = tcx.erase_regions((self_ty, target_principal, supertrait_principal)); let label2 = tcx .associated_items(item.owner_id) .find_by_ident_and_kind( tcx, Ident::with_dummy_span(sym::Target), ty::AssocTag::Type, item.owner_id.to_def_id(), ) .map(|label| SupertraitAsDerefTargetLabel { label: tcx.def_span(label.def_id) }); let span = tcx.def_span(item.owner_id.def_id); cx.emit_span_lint( DEREF_INTO_DYN_SUPERTRAIT, span, SupertraitAsDerefTarget { self_ty, supertrait_principal: supertrait_principal.map_bound(|trait_ref| { ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) }), target_principal, label: span, label2, }, ); } } }