diff --git a/Cargo.lock b/Cargo.lock index a4e1ab6c1e4a8..44dabad28ee7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4457,6 +4457,7 @@ dependencies = [ "rustc_hir", "rustc_index", "rustc_infer", + "rustc_lint_defs", "rustc_macros", "rustc_middle", "rustc_parse_format", diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 5830ce26fea3f..afe71fa45582b 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3050,6 +3050,7 @@ declare_lint_pass! { BREAK_WITH_LABEL_AND_LOOP, UNUSED_ATTRIBUTES, NON_EXHAUSTIVE_OMITTED_PATTERNS, + DEREF_INTO_DYN_SUPERTRAIT, ] } @@ -3511,3 +3512,48 @@ declare_lint! { Allow, "detect when patterns of types marked `non_exhaustive` are missed", } + +declare_lint! { + /// The `deref_into_dyn_supertrait` lint is output whenever there is a use of the + /// `Deref` implementation with a `dyn SuperTrait` type as `Output`. + /// + /// These implementations will become shadowed when the `trait_upcasting` feature is stablized. + /// The `deref` functions will no longer be called implicitly, so there might be behavior change. + /// + /// ### 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 dyn upcasting coercion feature adds new coercion rules, taking priority + /// over certain other coercion rules, which will cause some behavior change. + pub DEREF_INTO_DYN_SUPERTRAIT, + Warn, + "`Deref` implementation usage with a supertrait trait object for output might be shadowed in the future", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #89460 ", + }; +} diff --git a/compiler/rustc_trait_selection/Cargo.toml b/compiler/rustc_trait_selection/Cargo.toml index 9a9737362c6c8..27cd5c2acace4 100644 --- a/compiler/rustc_trait_selection/Cargo.toml +++ b/compiler/rustc_trait_selection/Cargo.toml @@ -17,6 +17,7 @@ rustc_errors = { path = "../rustc_errors" } rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_infer = { path = "../rustc_infer" } +rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index 719412492f637..f3706aa6e71aa 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -6,12 +6,17 @@ //! //! [rustc dev guide]:https://rustc-dev-guide.rust-lang.org/traits/resolution.html#candidate-assembly use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_infer::traits::TraitEngine; use rustc_infer::traits::{Obligation, SelectionError, TraitObligation}; +use rustc_lint_defs::builtin::DEREF_INTO_DYN_SUPERTRAIT; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, Ty, TypeFoldable}; +use rustc_middle::ty::{self, ToPredicate, Ty, TypeFoldable, WithConstness}; use rustc_target::spec::abi::Abi; +use crate::traits; use crate::traits::coherence::Conflict; +use crate::traits::query::evaluate_obligation::InferCtxtExt; use crate::traits::{util, SelectionResult}; use crate::traits::{Overflow, Unimplemented}; @@ -672,6 +677,55 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { }) } + /// Temporary migration for #89190 + fn need_migrate_deref_output_trait_object( + &mut self, + ty: Ty<'tcx>, + cause: &traits::ObligationCause<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option<(Ty<'tcx>, DefId)> { + let tcx = self.tcx(); + if tcx.features().trait_upcasting { + return None; + } + + // + let trait_ref = ty::TraitRef { + def_id: tcx.lang_items().deref_trait()?, + substs: tcx.mk_substs_trait(ty, &[]), + }; + + let obligation = traits::Obligation::new( + cause.clone(), + param_env, + ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx), + ); + if !self.infcx.predicate_may_hold(&obligation) { + return None; + } + + let mut fulfillcx = traits::FulfillmentContext::new_in_snapshot(); + let normalized_ty = fulfillcx.normalize_projection_type( + &self.infcx, + param_env, + ty::ProjectionTy { + item_def_id: tcx.lang_items().deref_target()?, + substs: trait_ref.substs, + }, + cause.clone(), + ); + + let data = if let ty::Dynamic(ref data, ..) = normalized_ty.kind() { + data + } else { + return None; + }; + + let def_id = data.principal_def_id()?; + + return Some((normalized_ty, def_id)); + } + /// Searches for unsizing that might apply to `obligation`. fn assemble_candidates_for_unsizing( &mut self, @@ -732,6 +786,30 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let principal_a = data_a.principal().unwrap(); let target_trait_did = principal_def_id_b.unwrap(); let source_trait_ref = principal_a.with_self_ty(self.tcx(), source); + if let Some((deref_output_ty, deref_output_trait_did)) = self + .need_migrate_deref_output_trait_object( + source, + &obligation.cause, + obligation.param_env, + ) + { + if deref_output_trait_did == target_trait_did { + self.tcx().struct_span_lint_hir( + DEREF_INTO_DYN_SUPERTRAIT, + obligation.cause.body_id, + obligation.cause.span, + |lint| { + lint.build(&format!( + "`{}` implements `Deref` with supertrait `{}` as output", + source, + deref_output_ty + )).emit(); + }, + ); + return; + } + } + for (idx, upcast_trait_ref) in util::supertraits(self.tcx(), source_trait_ref).enumerate() { diff --git a/src/test/ui/traits/trait-upcasting/migrate-lint-deny.rs b/src/test/ui/traits/trait-upcasting/migrate-lint-deny.rs new file mode 100644 index 0000000000000..c6725101858eb --- /dev/null +++ b/src/test/ui/traits/trait-upcasting/migrate-lint-deny.rs @@ -0,0 +1,25 @@ +#![deny(deref_into_dyn_supertrait)] + +extern crate core; + +use core::ops::Deref; + +// issue 89190 +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 whoops(b: &dyn B) { + take_a(b) + //~^ ERROR `dyn B` implements `Deref` with supertrait `(dyn A + 'static)` as output + //~^^ WARN this was previously accepted by the compiler but is being phased out; +} + +fn main() {} diff --git a/src/test/ui/traits/trait-upcasting/migrate-lint-deny.stderr b/src/test/ui/traits/trait-upcasting/migrate-lint-deny.stderr new file mode 100644 index 0000000000000..35af9112a27fc --- /dev/null +++ b/src/test/ui/traits/trait-upcasting/migrate-lint-deny.stderr @@ -0,0 +1,16 @@ +error: `dyn B` implements `Deref` with supertrait `(dyn A + 'static)` as output + --> $DIR/migrate-lint-deny.rs:20:12 + | +LL | take_a(b) + | ^ + | +note: the lint level is defined here + --> $DIR/migrate-lint-deny.rs:1:9 + | +LL | #![deny(deref_into_dyn_supertrait)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #89460 + +error: aborting due to previous error +