diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 67419fdf1c3e5..d7b5c1ed5e15b 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -41,8 +41,8 @@ use rustc_abi::ExternAbi; use rustc_attr_parsing::InlineAttr; use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, struct_span_code_err}; -use rustc_hir as hir; use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, LangItem}; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; use rustc_infer::infer::relate::RelateResult; use rustc_infer::infer::{Coercion, DefineOpaqueTypes, InferOk, InferResult}; @@ -56,7 +56,7 @@ use rustc_middle::ty::adjustment::{ }; use rustc_middle::ty::error::TypeError; use rustc_middle::ty::visit::TypeVisitableExt; -use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt}; +use rustc_middle::ty::{self, AliasTy, GenericArgsRef, Ty, TyCtxt}; use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; @@ -593,6 +593,63 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // Create an obligation for `Source: CoerceUnsized`. let cause = self.cause(self.cause.span, ObligationCauseCode::Coercion { source, target }); + let root_obligation = Obligation::new( + self.tcx, + cause.clone(), + self.fcx.param_env, + ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target]), + ); + + // If the root `Source: CoerceUnsized` obligation can't possibly hold, + // we don't have to assume that this is unsizing coercion (it will always lead to an error) + // + // However, we don't want to bail early all the time, since the unholdable obligations + // may be interesting for diagnostics (such as trying to coerce `&T` to `&dyn Id`), + // so we only bail if there (likely) is another way to convert the types. + if !self.infcx.predicate_may_hold(&root_obligation) { + if let Some(dyn_metadata_adt_def_id) = self.tcx.lang_items().get(LangItem::DynMetadata) + && let Some(metadata_type_def_id) = self.tcx.lang_items().get(LangItem::Metadata) + { + self.probe(|_| { + let ocx = ObligationCtxt::new(&self.infcx); + + // returns `true` if `::Metadata` is `DynMetadata<_>` + let has_dyn_trait_metadata = |ty| { + let metadata_ty: Result<_, _> = ocx.structurally_normalize_ty( + &ObligationCause::dummy(), + self.fcx.param_env, + Ty::new_alias( + self.tcx, + ty::AliasTyKind::Projection, + AliasTy::new(self.tcx, metadata_type_def_id, [ty]), + ), + ); + + metadata_ty.is_ok_and(|metadata_ty| { + metadata_ty + .ty_adt_def() + .is_some_and(|d| d.did() == dyn_metadata_adt_def_id) + }) + }; + + // If both types are raw pointers to a (wrapper over a) trait object, + // this might be a cast like `*const W -> *const dyn Trait`. + // So it's better to bail and try that. (even if the cast is not possible, for + // example due to vtables not matching, cast diagnostic will likely still be better) + // + // N.B. use `target`, not `coerce_target` (the latter is a var) + if let &ty::RawPtr(source_pointee, _) = coerce_source.kind() + && let &ty::RawPtr(target_pointee, _) = target.kind() + && has_dyn_trait_metadata(source_pointee) + && has_dyn_trait_metadata(target_pointee) + { + return Err(TypeError::Mismatch); + } + + Ok(()) + })?; + } + } // Use a FIFO queue for this custom fulfillment procedure. // @@ -601,12 +658,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // and almost never more than 3. By using a SmallVec we avoid an // allocation, at the (very small) cost of (occasionally) having to // shift subsequent elements down when removing the front element. - let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![Obligation::new( - self.tcx, - cause, - self.fcx.param_env, - ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target]) - )]; + let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![root_obligation]; // Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid // emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.rs b/tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.rs new file mode 100644 index 0000000000000..cfc0a97989dc1 --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.rs @@ -0,0 +1,36 @@ +// Combination of `ptr-to-trait-obj-wrap.rs` and `ptr-to-trait-obj-add-auto.rs`. +// +// Checks that you *can't* add auto traits to trait object in pointer casts involving wrapping said +// traits structures. + +trait A {} + +struct W(T); +struct X(T); + +fn unwrap(a: *const W) -> *const (dyn A + Send) { + a as _ + //~^ error: cannot add auto trait `Send` to dyn bound via pointer cast +} + +fn unwrap_nested(a: *const W>) -> *const W { + a as _ + //~^ error: cannot add auto trait `Send` to dyn bound via pointer cast +} + +fn rewrap(a: *const W) -> *const X { + a as _ + //~^ error: cannot add auto trait `Send` to dyn bound via pointer cast +} + +fn rewrap_nested(a: *const W>) -> *const W> { + a as _ + //~^ error: cannot add auto trait `Send` to dyn bound via pointer cast +} + +fn wrap(a: *const dyn A) -> *const W { + a as _ + //~^ error: cannot add auto trait `Send` to dyn bound via pointer cast +} + +fn main() {} diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.stderr b/tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.stderr new file mode 100644 index 0000000000000..42cdbc34ee827 --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.stderr @@ -0,0 +1,48 @@ +error[E0804]: cannot add auto trait `Send` to dyn bound via pointer cast + --> $DIR/ptr-to-trait-obj-wrap-add-auto.rs:12:5 + | +LL | a as _ + | ^^^^^^ unsupported cast + | + = note: this could allow UB elsewhere + = help: use `transmute` if you're sure this is sound + +error[E0804]: cannot add auto trait `Send` to dyn bound via pointer cast + --> $DIR/ptr-to-trait-obj-wrap-add-auto.rs:17:5 + | +LL | a as _ + | ^^^^^^ unsupported cast + | + = note: this could allow UB elsewhere + = help: use `transmute` if you're sure this is sound + +error[E0804]: cannot add auto trait `Send` to dyn bound via pointer cast + --> $DIR/ptr-to-trait-obj-wrap-add-auto.rs:22:5 + | +LL | a as _ + | ^^^^^^ unsupported cast + | + = note: this could allow UB elsewhere + = help: use `transmute` if you're sure this is sound + +error[E0804]: cannot add auto trait `Send` to dyn bound via pointer cast + --> $DIR/ptr-to-trait-obj-wrap-add-auto.rs:27:5 + | +LL | a as _ + | ^^^^^^ unsupported cast + | + = note: this could allow UB elsewhere + = help: use `transmute` if you're sure this is sound + +error[E0804]: cannot add auto trait `Send` to dyn bound via pointer cast + --> $DIR/ptr-to-trait-obj-wrap-add-auto.rs:32:5 + | +LL | a as _ + | ^^^^^^ unsupported cast + | + = note: this could allow UB elsewhere + = help: use `transmute` if you're sure this is sound + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0804`. diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap-different-args.rs b/tests/ui/cast/ptr-to-trait-obj-wrap-different-args.rs new file mode 100644 index 0000000000000..ebe7a06a7a158 --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap-different-args.rs @@ -0,0 +1,36 @@ +// Combination of `ptr-to-trait-obj-different-args.rs` and `ptr-to-trait-obj-wrap.rs`. +// +// Checks that you *can't* change type arguments of trait objects in pointer casts involving +// wrapping said traits structures. + +trait A {} + +struct W(T); +struct X(T); + +fn unwrap(a: *const W>) -> *const dyn A { + a as _ + //~^ error casting `*const W<(dyn A + 'static)>` as `*const dyn A` is invalid +} + +fn unwrap_nested(a: *const W>>) -> *const W> { + a as _ + //~^ error casting `*const W + 'static)>>` as `*const W>` is invalid +} + +fn rewrap(a: *const W>) -> *const X> { + a as _ + //~^ error: casting `*const W<(dyn A + 'static)>` as `*const X>` is invalid +} + +fn rewrap_nested(a: *const W>>) -> *const W>> { + a as _ + //~^ error: casting `*const W + 'static)>>` as `*const W>>` is invalid +} + +fn wrap(a: *const dyn A) -> *const W> { + a as _ + //~^ error: casting `*const (dyn A + 'static)` as `*const W>` is invalid +} + +fn main() {} diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap-different-args.stderr b/tests/ui/cast/ptr-to-trait-obj-wrap-different-args.stderr new file mode 100644 index 0000000000000..4f85b208d05ac --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap-different-args.stderr @@ -0,0 +1,43 @@ +error[E0606]: casting `*const W<(dyn A + 'static)>` as `*const dyn A` is invalid + --> $DIR/ptr-to-trait-obj-wrap-different-args.rs:12:5 + | +LL | a as _ + | ^^^^^^ + | + = note: the trait objects may have different vtables + +error[E0606]: casting `*const W + 'static)>>` as `*const W>` is invalid + --> $DIR/ptr-to-trait-obj-wrap-different-args.rs:17:5 + | +LL | a as _ + | ^^^^^^ + | + = note: the trait objects may have different vtables + +error[E0606]: casting `*const W<(dyn A + 'static)>` as `*const X>` is invalid + --> $DIR/ptr-to-trait-obj-wrap-different-args.rs:22:5 + | +LL | a as _ + | ^^^^^^ + | + = note: the trait objects may have different vtables + +error[E0606]: casting `*const W + 'static)>>` as `*const W>>` is invalid + --> $DIR/ptr-to-trait-obj-wrap-different-args.rs:27:5 + | +LL | a as _ + | ^^^^^^ + | + = note: the trait objects may have different vtables + +error[E0606]: casting `*const (dyn A + 'static)` as `*const W>` is invalid + --> $DIR/ptr-to-trait-obj-wrap-different-args.rs:32:5 + | +LL | a as _ + | ^^^^^^ + | + = note: the trait objects may have different vtables + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0606`. diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.rs b/tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.rs new file mode 100644 index 0000000000000..b0941277d01d3 --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.rs @@ -0,0 +1,41 @@ +// Combination of `ptr-to-trait-obj-different-regions-misc.rs` and `ptr-to-trait-obj-wrap.rs`. +// +// Checks that you *can't* change lifetime arguments of trait objects in pointer casts involving +// wrapping said traits structures. + +trait A<'a> {} + +struct W(T); +struct X(T); + +fn unwrap<'a, 'b>(a: *const W>) -> *const dyn A<'b> { + a as _ + //~^ error + //~| error +} + +fn unwrap_nested<'a, 'b>(a: *const W>>) -> *const W> { + a as _ + //~^ error + //~| error +} + +fn rewrap<'a, 'b>(a: *const W>) -> *const X> { + a as _ + //~^ error: lifetime may not live long enough + //~| error: lifetime may not live long enough +} + +fn rewrap_nested<'a, 'b>(a: *const W>>) -> *const W>> { + a as _ + //~^ error: lifetime may not live long enough + //~| error: lifetime may not live long enough +} + +fn wrap<'a, 'b>(a: *const dyn A<'a>) -> *const W> { + a as _ + //~^ error: lifetime may not live long enough + //~| error: lifetime may not live long enough +} + +fn main() {} diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.stderr b/tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.stderr new file mode 100644 index 0000000000000..17a0ca3c34fc8 --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.stderr @@ -0,0 +1,140 @@ +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:12:5 + | +LL | fn unwrap<'a, 'b>(a: *const W>) -> *const dyn A<'b> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b` + | + = help: consider adding the following bound: `'b: 'a` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:12:5 + | +LL | fn unwrap<'a, 'b>(a: *const W>) -> *const dyn A<'b> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` + | + = help: consider adding the following bound: `'a: 'b` + +help: `'b` and `'a` must be the same: replace one with the other + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:18:5 + | +LL | fn unwrap_nested<'a, 'b>(a: *const W>>) -> *const W> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b` + | + = help: consider adding the following bound: `'b: 'a` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:18:5 + | +LL | fn unwrap_nested<'a, 'b>(a: *const W>>) -> *const W> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` + | + = help: consider adding the following bound: `'a: 'b` + +help: `'b` and `'a` must be the same: replace one with the other + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:24:5 + | +LL | fn rewrap<'a, 'b>(a: *const W>) -> *const X> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b` + | + = help: consider adding the following bound: `'b: 'a` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:24:5 + | +LL | fn rewrap<'a, 'b>(a: *const W>) -> *const X> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` + | + = help: consider adding the following bound: `'a: 'b` + +help: `'b` and `'a` must be the same: replace one with the other + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:30:5 + | +LL | fn rewrap_nested<'a, 'b>(a: *const W>>) -> *const W>> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b` + | + = help: consider adding the following bound: `'b: 'a` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:30:5 + | +LL | fn rewrap_nested<'a, 'b>(a: *const W>>) -> *const W>> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` + | + = help: consider adding the following bound: `'a: 'b` + +help: `'b` and `'a` must be the same: replace one with the other + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:36:5 + | +LL | fn wrap<'a, 'b>(a: *const dyn A<'a>) -> *const W> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b` + | + = help: consider adding the following bound: `'b: 'a` + +error: lifetime may not live long enough + --> $DIR/ptr-to-trait-obj-wrap-different-regions.rs:36:5 + | +LL | fn wrap<'a, 'b>(a: *const dyn A<'a>) -> *const W> { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | a as _ + | ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` + | + = help: consider adding the following bound: `'a: 'b` + +help: `'b` and `'a` must be the same: replace one with the other + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 10 previous errors + diff --git a/tests/ui/cast/ptr-to-trait-obj-wrap.rs b/tests/ui/cast/ptr-to-trait-obj-wrap.rs new file mode 100644 index 0000000000000..6f9f6bddb996b --- /dev/null +++ b/tests/ui/cast/ptr-to-trait-obj-wrap.rs @@ -0,0 +1,33 @@ +// Checks that various casts of pointers to trait objects wrapped in structures +// work. Note that the metadata doesn't change when a DST is wrapped in a +// structure, so these casts *are* fine. +// +// `unwrap` and `unwrap_nested` currently don't work due to a compiler limitation. +//@ check-pass + +trait A {} + +struct W(T); +struct X(T); + +fn unwrap(a: *const W) -> *const dyn A { + a as _ +} + +fn unwrap_nested(a: *const W>) -> *const W { + a as _ +} + +fn rewrap(a: *const W) -> *const X { + a as _ +} + +fn rewrap_nested(a: *const W>) -> *const W> { + a as _ +} + +fn wrap(a: *const dyn A) -> *const W { + a as _ +} + +fn main() {}