Skip to content

Commit

Permalink
Rollup merge of #136127 - WaffleLapkin:dyn_ptr_unwrap_cast, r=compile…
Browse files Browse the repository at this point in the history
…r-errors

Allow `*const W<dyn A> -> *const dyn A` ptr cast

Followup of #120248 (comment).

This PR allows casting pointers from something wrapping a trait object, to the trait object, i.e. `*const W<dyn A> -> *const dyn A` where `W` is `struct W<T: ?Sized>(T);`.

r? compiler-errors

Fixes #128625
  • Loading branch information
matthiaskrgr authored Mar 9, 2025
2 parents a96fa31 + 80157a5 commit 84c2050
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 8 deletions.
68 changes: 60 additions & 8 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -593,6 +593,63 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {

// Create an obligation for `Source: CoerceUnsized<Target>`.
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<Target>` 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<This = U>`),
// 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 `<ty as Pointee>::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<dyn Trait> -> *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.
//
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.rs
Original file line number Diff line number Diff line change
@@ -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: ?Sized>(T);
struct X<T: ?Sized>(T);

fn unwrap(a: *const W<dyn A>) -> *const (dyn A + Send) {
a as _
//~^ error: cannot add auto trait `Send` to dyn bound via pointer cast
}

fn unwrap_nested(a: *const W<W<dyn A>>) -> *const W<dyn A + Send> {
a as _
//~^ error: cannot add auto trait `Send` to dyn bound via pointer cast
}

fn rewrap(a: *const W<dyn A>) -> *const X<dyn A + Send> {
a as _
//~^ error: cannot add auto trait `Send` to dyn bound via pointer cast
}

fn rewrap_nested(a: *const W<W<dyn A>>) -> *const W<X<dyn A + Send>> {
a as _
//~^ error: cannot add auto trait `Send` to dyn bound via pointer cast
}

fn wrap(a: *const dyn A) -> *const W<dyn A + Send> {
a as _
//~^ error: cannot add auto trait `Send` to dyn bound via pointer cast
}

fn main() {}
48 changes: 48 additions & 0 deletions tests/ui/cast/ptr-to-trait-obj-wrap-add-auto.stderr
Original file line number Diff line number Diff line change
@@ -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`.
36 changes: 36 additions & 0 deletions tests/ui/cast/ptr-to-trait-obj-wrap-different-args.rs
Original file line number Diff line number Diff line change
@@ -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<T> {}

struct W<T: ?Sized>(T);
struct X<T: ?Sized>(T);

fn unwrap<F, G>(a: *const W<dyn A<F>>) -> *const dyn A<G> {
a as _
//~^ error casting `*const W<(dyn A<F> + 'static)>` as `*const dyn A<G>` is invalid
}

fn unwrap_nested<F, G>(a: *const W<W<dyn A<F>>>) -> *const W<dyn A<G>> {
a as _
//~^ error casting `*const W<W<(dyn A<F> + 'static)>>` as `*const W<dyn A<G>>` is invalid
}

fn rewrap<F, G>(a: *const W<dyn A<F>>) -> *const X<dyn A<G>> {
a as _
//~^ error: casting `*const W<(dyn A<F> + 'static)>` as `*const X<dyn A<G>>` is invalid
}

fn rewrap_nested<F, G>(a: *const W<W<dyn A<F>>>) -> *const W<X<dyn A<G>>> {
a as _
//~^ error: casting `*const W<W<(dyn A<F> + 'static)>>` as `*const W<X<dyn A<G>>>` is invalid
}

fn wrap<F, G>(a: *const dyn A<F>) -> *const W<dyn A<G>> {
a as _
//~^ error: casting `*const (dyn A<F> + 'static)` as `*const W<dyn A<G>>` is invalid
}

fn main() {}
43 changes: 43 additions & 0 deletions tests/ui/cast/ptr-to-trait-obj-wrap-different-args.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
error[E0606]: casting `*const W<(dyn A<F> + 'static)>` as `*const dyn A<G>` 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<W<(dyn A<F> + 'static)>>` as `*const W<dyn A<G>>` 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<F> + 'static)>` as `*const X<dyn A<G>>` 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<W<(dyn A<F> + 'static)>>` as `*const W<X<dyn A<G>>>` 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<F> + 'static)` as `*const W<dyn A<G>>` 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`.
41 changes: 41 additions & 0 deletions tests/ui/cast/ptr-to-trait-obj-wrap-different-regions.rs
Original file line number Diff line number Diff line change
@@ -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: ?Sized>(T);
struct X<T: ?Sized>(T);

fn unwrap<'a, 'b>(a: *const W<dyn A<'a>>) -> *const dyn A<'b> {
a as _
//~^ error
//~| error
}

fn unwrap_nested<'a, 'b>(a: *const W<W<dyn A<'a>>>) -> *const W<dyn A<'b>> {
a as _
//~^ error
//~| error
}

fn rewrap<'a, 'b>(a: *const W<dyn A<'a>>) -> *const X<dyn A<'b>> {
a as _
//~^ error: lifetime may not live long enough
//~| error: lifetime may not live long enough
}

fn rewrap_nested<'a, 'b>(a: *const W<W<dyn A<'a>>>) -> *const W<X<dyn A<'b>>> {
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<dyn A<'b>> {
a as _
//~^ error: lifetime may not live long enough
//~| error: lifetime may not live long enough
}

fn main() {}
Loading

0 comments on commit 84c2050

Please sign in to comment.