Rollup merge of #120354 - lukas-code:metadata-normalize, r=lcnr
improve normalization of `Pointee::Metadata`
This PR makes it so that `<Wrapper<Tail> as Pointee>::Metadata` is normalized to `<Tail as Pointee>::Metadata` if we don't know `Wrapper<Tail>: Sized`. With that, the trait solver can prove projection predicates like `<Wrapper<Tail> as Pointee>::Metadata == <Tail as Pointee>::Metadata`, which makes it possible to use the metadata APIs to cast between the tail and the wrapper:
```rust
#![feature(ptr_metadata)]
use std::ptr::{self, Pointee};
fn cast_same_meta<T: ?Sized, U: ?Sized>(ptr: *const T) -> *const U
where
T: Pointee<Metadata = <U as Pointee>::Metadata>,
{
let (thin, meta) = ptr.to_raw_parts();
ptr::from_raw_parts(thin, meta)
}
struct Wrapper<T: ?Sized>(T);
fn cast_to_wrapper<T: ?Sized>(ptr: *const T) -> *const Wrapper<T> {
cast_same_meta(ptr)
}
```
Previously, this failed to compile:
```
error[E0271]: type mismatch resolving `<Wrapper<T> as Pointee>::Metadata == <T as Pointee>::Metadata`
--> src/lib.rs:16:5
|
15 | fn cast_to_wrapper<T: ?Sized>(ptr: *const T) -> *const Wrapper<T> {
| - found this type parameter
16 | cast_same_meta(ptr)
| ^^^^^^^^^^^^^^ expected `Wrapper<T>`, found type parameter `T`
|
= note: expected associated type `<Wrapper<T> as Pointee>::Metadata`
found associated type `<T as Pointee>::Metadata`
= note: an associated type was expected, but a different one was found
```
(Yes, you can already do this with `as` casts. But using functions is so much ✨ *safer* ✨, because you can't change the metadata on accident.)
---
This PR essentially changes the built-in impls of `Pointee` from this:
```rust
// before
impl Pointee for u8 {
type Metadata = ();
}
impl Pointee for [u8] {
type Metadata = usize;
}
// ...
impl Pointee for Wrapper<u8> {
type Metadata = ();
}
impl Pointee for Wrapper<[u8]> {
type Metadata = usize;
}
// ...
// This impl is only selected if `T` is a type parameter or unnormalizable projection or opaque type.
fallback impl<T: ?Sized> Pointee for Wrapper<T>
where
Wrapper<T>: Sized
{
type Metadata = ();
}
// This impl is only selected if `T` is a type parameter or unnormalizable projection or opaque type.
fallback impl<T /*: Sized */> Pointee for T {
type Metadata = ();
}
```
to this:
```rust
// after
impl Pointee for u8 {
type Metadata = ();
}
impl Pointee for [u8] {
type Metadata = usize;
}
// ...
impl<T: ?Sized> Pointee for Wrapper<T> {
// in the old solver this will instead project to the "deep" tail directly,
// e.g. `Wrapper<Wrapper<T>>::Metadata = T::Metadata`
type Metadata = <T as Pointee>::Metadata;
}
// ...
// This impl is only selected if `T` is a type parameter or unnormalizable projection or opaque type.
fallback impl<T /*: Sized */> Pointee for T {
type Metadata = ();
}
```
This commit is contained in:
@@ -491,6 +491,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
|
||||
goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let tcx = ecx.tcx();
|
||||
let metadata_def_id = tcx.require_lang_item(LangItem::Metadata, None);
|
||||
assert_eq!(metadata_def_id, goal.predicate.def_id());
|
||||
ecx.probe_misc_candidate("builtin pointee").enter(|ecx| {
|
||||
let metadata_ty = match goal.predicate.self_ty().kind() {
|
||||
ty::Bool
|
||||
@@ -522,7 +524,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
|
||||
}
|
||||
|
||||
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
|
||||
// FIXME(ptr_metadata): It would also be possible to return a `Ok(Ambig)` with no constraints.
|
||||
// This is the "fallback impl" for type parameters, unnormalizable projections
|
||||
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
|
||||
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
|
||||
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
|
||||
let sized_predicate = ty::TraitRef::from_lang_item(
|
||||
tcx,
|
||||
LangItem::Sized,
|
||||
@@ -536,30 +541,16 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
|
||||
|
||||
ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() {
|
||||
None => tcx.types.unit,
|
||||
Some(field_def) => {
|
||||
let self_ty = field_def.ty(tcx, args);
|
||||
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
|
||||
ecx.add_goal(
|
||||
GoalSource::Misc,
|
||||
goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty)),
|
||||
);
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
Some(tail_def) => {
|
||||
let tail_ty = tail_def.ty(tcx, args);
|
||||
Ty::new_projection(tcx, metadata_def_id, [tail_ty])
|
||||
}
|
||||
},
|
||||
ty::Adt(_, _) => tcx.types.unit,
|
||||
|
||||
ty::Tuple(elements) => match elements.last() {
|
||||
None => tcx.types.unit,
|
||||
Some(&self_ty) => {
|
||||
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
|
||||
ecx.add_goal(
|
||||
GoalSource::Misc,
|
||||
goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty)),
|
||||
);
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
}
|
||||
Some(&tail_ty) => Ty::new_projection(tcx, metadata_def_id, [tail_ty]),
|
||||
},
|
||||
|
||||
ty::Infer(
|
||||
|
||||
@@ -1935,10 +1935,11 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
|
||||
// Integers and floats are always Sized, and so have unit type metadata.
|
||||
| ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..)) => true,
|
||||
|
||||
// type parameters, opaques, and unnormalized projections have pointer
|
||||
// metadata if they're known (e.g. by the param_env) to be sized
|
||||
// We normalize from `Wrapper<Tail>::Metadata` to `Tail::Metadata` if able.
|
||||
// Otherwise, type parameters, opaques, and unnormalized projections have
|
||||
// unit metadata if they're known (e.g. by the param_env) to be sized.
|
||||
ty::Param(_) | ty::Alias(..)
|
||||
if selcx.infcx.predicate_must_hold_modulo_regions(
|
||||
if self_ty != tail || selcx.infcx.predicate_must_hold_modulo_regions(
|
||||
&obligation.with(
|
||||
selcx.tcx(),
|
||||
ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty]),
|
||||
@@ -2312,7 +2313,7 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
|
||||
assert_eq!(metadata_def_id, item_def_id);
|
||||
|
||||
let mut obligations = Vec::new();
|
||||
let (metadata_ty, check_is_sized) = self_ty.ptr_metadata_ty(tcx, |ty| {
|
||||
let normalize = |ty| {
|
||||
normalize_with_depth_to(
|
||||
selcx,
|
||||
obligation.param_env,
|
||||
@@ -2321,16 +2322,27 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
|
||||
ty,
|
||||
&mut obligations,
|
||||
)
|
||||
};
|
||||
let metadata_ty = self_ty.ptr_metadata_ty_or_tail(tcx, normalize).unwrap_or_else(|tail| {
|
||||
if tail == self_ty {
|
||||
// This is the "fallback impl" for type parameters, unnormalizable projections
|
||||
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
|
||||
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
|
||||
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
|
||||
let sized_predicate = ty::TraitRef::from_lang_item(
|
||||
tcx,
|
||||
LangItem::Sized,
|
||||
obligation.cause.span(),
|
||||
[self_ty],
|
||||
);
|
||||
obligations.push(obligation.with(tcx, sized_predicate));
|
||||
tcx.types.unit
|
||||
} else {
|
||||
// We know that `self_ty` has the same metadata as `tail`. This allows us
|
||||
// to prove predicates like `Wrapper<Tail>::Metadata == Tail::Metadata`.
|
||||
Ty::new_projection(tcx, metadata_def_id, [tail])
|
||||
}
|
||||
});
|
||||
if check_is_sized {
|
||||
let sized_predicate = ty::TraitRef::from_lang_item(
|
||||
tcx,
|
||||
LangItem::Sized,
|
||||
obligation.cause.span(),
|
||||
[self_ty],
|
||||
);
|
||||
obligations.push(obligation.with(tcx, sized_predicate));
|
||||
}
|
||||
(metadata_ty.into(), obligations)
|
||||
} else {
|
||||
bug!("unexpected builtin trait with associated type: {:?}", obligation.predicate);
|
||||
|
||||
@@ -41,7 +41,28 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
||||
/// not entirely accurate if inference variables are involved.
|
||||
///
|
||||
/// This version may conservatively fail when outlives obligations
|
||||
/// are required.
|
||||
/// are required. Therefore, this version should only be used for
|
||||
/// optimizations or diagnostics and be treated as if it can always
|
||||
/// return `false`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #![allow(dead_code)]
|
||||
/// trait Trait {}
|
||||
///
|
||||
/// fn check<T: Trait>() {}
|
||||
///
|
||||
/// fn foo<T: 'static>()
|
||||
/// where
|
||||
/// &'static T: Trait,
|
||||
/// {
|
||||
/// // Evaluating `&'?0 T: Trait` adds a `'?0: 'static` outlives obligation,
|
||||
/// // which means that `predicate_must_hold_considering_regions` will return
|
||||
/// // `false`.
|
||||
/// check::<&'_ T>();
|
||||
/// }
|
||||
/// ```
|
||||
fn predicate_must_hold_considering_regions(
|
||||
&self,
|
||||
obligation: &PredicateObligation<'tcx>,
|
||||
|
||||
Reference in New Issue
Block a user