Auto merge of #110662 - bryangarza:safe-transmute-reference-types, r=compiler-errors

Safe Transmute: Enable handling references

This patch enables support for references in Safe Transmute, by generating nested obligations during trait selection. Specifically, when we call `confirm_transmutability_candidate(...)`, we now recursively traverse the `rustc_transmute::Answer` tree and create obligations for all the `Answer` variants, some of which include multiple nested `Answer`s.
This commit is contained in:
bors 2023-06-14 08:26:22 +00:00
commit 3ed2a10d17
32 changed files with 788 additions and 205 deletions

View File

@ -709,6 +709,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
scope: Ty<'tcx>,
assume: rustc_transmute::Assume,
) -> Result<Certainty, NoSolution> {
use rustc_transmute::Answer;
// FIXME(transmutability): This really should be returning nested goals for `Answer::If*`
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
ObligationCause::dummy(),
@ -716,11 +717,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
scope,
assume,
) {
rustc_transmute::Answer::Yes => Ok(Certainty::Yes),
rustc_transmute::Answer::No(_)
| rustc_transmute::Answer::IfTransmutable { .. }
| rustc_transmute::Answer::IfAll(_)
| rustc_transmute::Answer::IfAny(_) => Err(NoSolution),
Answer::Yes => Ok(Certainty::Yes),
Answer::No(_) | Answer::If(_) => Err(NoSolution),
}
}

View File

@ -66,6 +66,11 @@ pub struct ImplCandidate<'tcx> {
pub similarity: CandidateSimilarity,
}
enum GetSafeTransmuteErrorAndReason {
Silent,
Error { err_msg: String, safe_transmute_explanation: String },
}
pub trait InferCtxtExt<'tcx> {
/// Given some node representing a fn-like thing in the HIR map,
/// returns a span and `ArgKind` information that describes the
@ -739,11 +744,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
== self.tcx.lang_items().transmute_trait()
{
// Recompute the safe transmute reason and use that for the error reporting
self.get_safe_transmute_error_and_reason(
match self.get_safe_transmute_error_and_reason(
obligation.clone(),
trait_ref,
span,
)
) {
GetSafeTransmuteErrorAndReason::Silent => return,
GetSafeTransmuteErrorAndReason::Error {
err_msg,
safe_transmute_explanation,
} => (err_msg, Some(safe_transmute_explanation)),
}
} else {
(err_msg, None)
};
@ -1403,7 +1414,7 @@ trait InferCtxtPrivExt<'tcx> {
obligation: PredicateObligation<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
span: Span,
) -> (String, Option<String>);
) -> GetSafeTransmuteErrorAndReason;
fn add_tuple_trait_message(
&self,
@ -2850,7 +2861,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
obligation: PredicateObligation<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
span: Span,
) -> (String, Option<String>) {
) -> GetSafeTransmuteErrorAndReason {
use rustc_transmute::Answer;
// Erase regions because layout code doesn't particularly care about regions.
let trait_ref = self.tcx.erase_regions(self.tcx.erase_late_bound_regions(trait_ref));
@ -2863,19 +2876,20 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, trait_ref.substs.const_at(3)) else {
span_bug!(span, "Unable to construct rustc_transmute::Assume where it was previously possible");
};
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
obligation.cause,
src_and_dst,
scope,
assume,
) {
rustc_transmute::Answer::No(reason) => {
Answer::No(reason) => {
let dst = trait_ref.substs.type_at(0);
let src = trait_ref.substs.type_at(1);
let custom_err_msg = format!(
let err_msg = format!(
"`{src}` cannot be safely transmuted into `{dst}` in the defining scope of `{scope}`"
);
let reason_msg = match reason {
let safe_transmute_explanation = match reason {
rustc_transmute::Reason::SrcIsUnspecified => {
format!("`{src}` does not have a well-specified layout")
}
@ -2891,19 +2905,39 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
rustc_transmute::Reason::DstIsPrivate => format!(
"`{dst}` is or contains a type or field that is not visible in that scope"
),
// FIXME(bryangarza): Include the number of bytes of src and dst
rustc_transmute::Reason::DstIsTooBig => {
format!("The size of `{src}` is smaller than the size of `{dst}`")
}
rustc_transmute::Reason::DstHasStricterAlignment {
src_min_align,
dst_min_align,
} => {
format!(
"The minimum alignment of `{src}` ({src_min_align}) should be greater than that of `{dst}` ({dst_min_align})"
)
}
rustc_transmute::Reason::DstIsMoreUnique => {
format!("`{src}` is a shared reference, but `{dst}` is a unique reference")
}
// Already reported by rustc
rustc_transmute::Reason::TypeError => {
return GetSafeTransmuteErrorAndReason::Silent;
}
rustc_transmute::Reason::SrcLayoutUnknown => {
format!("`{src}` has an unknown layout")
}
rustc_transmute::Reason::DstLayoutUnknown => {
format!("`{dst}` has an unknown layout")
}
};
(custom_err_msg, Some(reason_msg))
GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation }
}
// Should never get a Yes at this point! We already ran it before, and did not get a Yes.
rustc_transmute::Answer::Yes => span_bug!(
Answer::Yes => span_bug!(
span,
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
),
_ => span_bug!(span, "Unsupported rustc_transmute::Reason variant"),
other => span_bug!(span, "Unsupported rustc_transmute::Answer variant: `{other:?}`"),
}
}

View File

@ -6,6 +6,7 @@
//!
//! [rustc dev guide]:
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
use rustc_ast::Mutability;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::LateBoundRegionConversionTime::HigherRankedType;
@ -13,7 +14,7 @@ use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
use rustc_middle::traits::SelectionOutputTypeParameterMismatch;
use rustc_middle::ty::{
self, Binder, GenericParamDefKind, InternalSubsts, SubstsRef, ToPolyTraitRef, ToPredicate,
TraitRef, Ty, TyCtxt, TypeVisitableExt,
TraitPredicate, TraitRef, Ty, TyCtxt, TypeVisitableExt,
};
use rustc_session::config::TraitSolver;
use rustc_span::def_id::DefId;
@ -279,11 +280,60 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
ImplSourceBuiltinData { nested: obligations }
}
#[instrument(level = "debug", skip(self))]
fn confirm_transmutability_candidate(
&mut self,
obligation: &TraitObligation<'tcx>,
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
debug!(?obligation, "confirm_transmutability_candidate");
use rustc_transmute::{Answer, Condition};
#[instrument(level = "debug", skip(tcx, obligation, predicate))]
fn flatten_answer_tree<'tcx>(
tcx: TyCtxt<'tcx>,
obligation: &TraitObligation<'tcx>,
predicate: TraitPredicate<'tcx>,
cond: Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
) -> Vec<PredicateObligation<'tcx>> {
match cond {
// FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll`
// Not possible until the trait solver supports disjunctions of obligations
Condition::IfAll(conds) | Condition::IfAny(conds) => conds
.into_iter()
.flat_map(|cond| flatten_answer_tree(tcx, obligation, predicate, cond))
.collect(),
Condition::IfTransmutable { src, dst } => {
let trait_def_id = obligation.predicate.def_id();
let scope = predicate.trait_ref.substs.type_at(2);
let assume_const = predicate.trait_ref.substs.const_at(3);
let make_obl = |from_ty, to_ty| {
let trait_ref1 = ty::TraitRef::new(
tcx,
trait_def_id,
[
ty::GenericArg::from(to_ty),
ty::GenericArg::from(from_ty),
ty::GenericArg::from(scope),
ty::GenericArg::from(assume_const),
],
);
Obligation::with_depth(
tcx,
obligation.cause.clone(),
obligation.recursion_depth + 1,
obligation.param_env,
trait_ref1,
)
};
// If Dst is mutable, check bidirectionally.
// For example, transmuting bool -> u8 is OK as long as you can't update that u8
// to be > 1, because you could later transmute the u8 back to a bool and get UB.
match dst.mutability {
Mutability::Not => vec![make_obl(src.ty, dst.ty)],
Mutability::Mut => vec![make_obl(src.ty, dst.ty), make_obl(dst.ty, src.ty)],
}
}
}
}
// We erase regions here because transmutability calls layout queries,
// which does not handle inference regions and doesn't particularly
@ -301,21 +351,25 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
return Err(Unimplemented);
};
let dst = predicate.trait_ref.substs.type_at(0);
let src = predicate.trait_ref.substs.type_at(1);
debug!(?src, ?dst);
let mut transmute_env = rustc_transmute::TransmuteTypeEnv::new(self.infcx);
let maybe_transmutable = transmute_env.is_transmutable(
obligation.cause.clone(),
rustc_transmute::Types {
dst: predicate.trait_ref.substs.type_at(0),
src: predicate.trait_ref.substs.type_at(1),
},
rustc_transmute::Types { dst, src },
predicate.trait_ref.substs.type_at(2),
assume,
);
match maybe_transmutable {
rustc_transmute::Answer::Yes => Ok(ImplSourceBuiltinData { nested: vec![] }),
_ => Err(Unimplemented),
}
let fully_flattened = match maybe_transmutable {
Answer::No(_) => Err(Unimplemented)?,
Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, predicate, cond),
Answer::Yes => vec![],
};
debug!(?fully_flattened);
Ok(ImplSourceBuiltinData { nested: fully_flattened })
}
/// This handles the case where an `auto trait Foo` impl is being used.

View File

@ -30,33 +30,49 @@ impl fmt::Debug for Byte {
}
pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone {}
pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {}
pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {
fn min_align(&self) -> usize;
fn is_mutable(&self) -> bool;
}
impl Def for ! {}
impl Ref for ! {}
impl Ref for ! {
fn min_align(&self) -> usize {
unreachable!()
}
fn is_mutable(&self) -> bool {
unreachable!()
}
}
#[cfg(feature = "rustc")]
pub(crate) mod rustc {
pub mod rustc {
use rustc_middle::mir::Mutability;
use rustc_middle::ty;
use rustc_middle::ty::Region;
use rustc_middle::ty::Ty;
use rustc_middle::ty::{self, Ty};
/// A reference in the layout.
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
pub struct Ref<'tcx> {
lifetime: Region<'tcx>,
ty: Ty<'tcx>,
mutability: Mutability,
pub lifetime: ty::Region<'tcx>,
pub ty: Ty<'tcx>,
pub mutability: Mutability,
pub align: usize,
}
impl<'tcx> super::Ref for Ref<'tcx> {}
impl<'tcx> super::Ref for Ref<'tcx> {
fn min_align(&self) -> usize {
self.align
}
impl<'tcx> Ref<'tcx> {
pub fn min_align(&self) -> usize {
todo!()
fn is_mutable(&self) -> bool {
match self.mutability {
Mutability::Mut => true,
Mutability::Not => false,
}
}
}
impl<'tcx> Ref<'tcx> {}
/// A visibility node in the layout.
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]

View File

@ -188,14 +188,14 @@ pub(crate) mod rustc {
/// The layout of the type is unspecified.
Unspecified,
/// This error will be surfaced elsewhere by rustc, so don't surface it.
Unknown,
UnknownLayout,
TypeError(ErrorGuaranteed),
}
impl<'tcx> From<LayoutError<'tcx>> for Err {
fn from(err: LayoutError<'tcx>) -> Self {
match err {
LayoutError::Unknown(..) => Self::Unknown,
LayoutError::Unknown(..) => Self::UnknownLayout,
err => unimplemented!("{:?}", err),
}
}
@ -365,6 +365,17 @@ pub(crate) mod rustc {
}
}))
}
ty::Ref(lifetime, ty, mutability) => {
let align = layout_of(tcx, *ty)?.align();
Ok(Tree::Ref(Ref {
lifetime: *lifetime,
ty: *ty,
mutability: *mutability,
align,
}))
}
_ => Err(Err::Unspecified),
}
}

View File

@ -8,7 +8,7 @@ extern crate tracing;
pub(crate) use rustc_data_structures::fx::{FxIndexMap as Map, FxIndexSet as Set};
pub(crate) mod layout;
pub mod layout;
pub(crate) mod maybe_transmutable;
#[derive(Default)]
@ -19,29 +19,29 @@ pub struct Assume {
pub validity: bool,
}
/// The type encodes answers to the question: "Are these types transmutable?"
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)]
pub enum Answer<R>
where
R: layout::Ref,
{
/// `Src` is transmutable into `Dst`.
/// Either we have an error, transmutation is allowed, or we have an optional
/// Condition that must hold.
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub enum Answer<R> {
Yes,
/// `Src` is NOT transmutable into `Dst`.
No(Reason),
If(Condition<R>),
}
/// A condition which must hold for safe transmutation to be possible.
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub enum Condition<R> {
/// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`.
IfTransmutable { src: R, dst: R },
/// `Src` is transmutable into `Dst`, if all of the enclosed requirements are met.
IfAll(Vec<Answer<R>>),
IfAll(Vec<Condition<R>>),
/// `Src` is transmutable into `Dst` if any of the enclosed requirements are met.
IfAny(Vec<Answer<R>>),
IfAny(Vec<Condition<R>>),
}
/// Answers: Why wasn't the source type transmutable into the destination type?
/// Answers "why wasn't the source type transmutable into the destination type?"
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)]
pub enum Reason {
/// The layout of the source type is unspecified.
@ -54,6 +54,16 @@ pub enum Reason {
DstIsPrivate,
/// `Dst` is larger than `Src`, and the excess bytes were not exclusively uninitialized.
DstIsTooBig,
/// Src should have a stricter alignment than Dst, but it does not.
DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize },
/// Can't go from shared pointer to unique pointer
DstIsMoreUnique,
/// Encountered a type error
TypeError,
/// The layout of src is unknown
SrcLayoutUnknown,
/// The layout of dst is unknown
DstLayoutUnknown,
}
#[cfg(feature = "rustc")]

View File

@ -1,13 +1,13 @@
use crate::Map;
use crate::{Answer, Reason};
pub(crate) mod query_context;
#[cfg(test)]
mod tests;
mod query_context;
use query_context::QueryContext;
use crate::{
layout::{self, dfa, Byte, Dfa, Nfa, Ref, Tree, Uninhabited},
maybe_transmutable::query_context::QueryContext,
Answer, Condition, Map, Reason,
};
use crate::layout::{self, dfa, Byte, Dfa, Nfa, Tree, Uninhabited};
pub(crate) struct MaybeTransmutableQuery<L, C>
where
C: QueryContext,
@ -33,6 +33,7 @@ where
Self { src, dst, scope, assume, context }
}
// FIXME(bryangarza): Delete this when all usages are removed
pub(crate) fn map_layouts<F, M>(
self,
f: F,
@ -53,6 +54,7 @@ where
}
}
// FIXME: Nix this cfg, so we can write unit tests independently of rustc
#[cfg(feature = "rustc")]
mod rustc {
use super::*;
@ -66,30 +68,26 @@ mod rustc {
/// then computes an answer using those trees.
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
pub fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> {
let query_or_answer = self.map_layouts(|src, dst, scope, &context| {
// Convert `src` and `dst` from their rustc representations, to `Tree`-based
// representations. If these conversions fail, conclude that the transmutation is
// unacceptable; the layouts of both the source and destination types must be
// well-defined.
let src = Tree::from_ty(src, context);
let dst = Tree::from_ty(dst, context);
let Self { src, dst, scope, assume, context } = self;
match (src, dst) {
// Answer `Yes` here, because 'unknown layout' and type errors will already
// be reported by rustc. No need to spam the user with more errors.
(Err(Err::TypeError(_)), _) => Err(Answer::Yes),
(_, Err(Err::TypeError(_))) => Err(Answer::Yes),
(Err(Err::Unknown), _) => Err(Answer::Yes),
(_, Err(Err::Unknown)) => Err(Answer::Yes),
(Err(Err::Unspecified), _) => Err(Answer::No(Reason::SrcIsUnspecified)),
(_, Err(Err::Unspecified)) => Err(Answer::No(Reason::DstIsUnspecified)),
(Ok(src), Ok(dst)) => Ok((src, dst)),
// Convert `src` and `dst` from their rustc representations, to `Tree`-based
// representations. If these conversions fail, conclude that the transmutation is
// unacceptable; the layouts of both the source and destination types must be
// well-defined.
let src = Tree::from_ty(src, context);
let dst = Tree::from_ty(dst, context);
match (src, dst) {
(Err(Err::TypeError(_)), _) | (_, Err(Err::TypeError(_))) => {
Answer::No(Reason::TypeError)
}
(Err(Err::UnknownLayout), _) => Answer::No(Reason::SrcLayoutUnknown),
(_, Err(Err::UnknownLayout)) => Answer::No(Reason::DstLayoutUnknown),
(Err(Err::Unspecified), _) => Answer::No(Reason::SrcIsUnspecified),
(_, Err(Err::Unspecified)) => Answer::No(Reason::DstIsUnspecified),
(Ok(src), Ok(dst)) => {
MaybeTransmutableQuery { src, dst, scope, assume, context }.answer()
}
});
match query_or_answer {
Ok(query) => query.answer(),
Err(answer) => answer,
}
}
}
@ -107,6 +105,7 @@ where
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
let assume_visibility = self.assume.safety;
// FIXME(bryangarza): Refactor this code to get rid of `map_layouts`
let query_or_answer = self.map_layouts(|src, dst, scope, context| {
// Remove all `Def` nodes from `src`, without checking their visibility.
let src = src.prune(&|def| true);
@ -155,6 +154,7 @@ where
#[inline(always)]
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
// FIXME(bryangarza): Refactor this code to get rid of `map_layouts`
let query_or_answer = self
.map_layouts(|src, dst, scope, context| Ok((Dfa::from_nfa(src), Dfa::from_nfa(dst))));
@ -203,8 +203,29 @@ where
if let Some(answer) = cache.get(&(src_state, dst_state)) {
answer.clone()
} else {
debug!(?src_state, ?dst_state);
debug!(src = ?self.src);
debug!(dst = ?self.dst);
debug!(
src_transitions_len = self.src.transitions.len(),
dst_transitions_len = self.dst.transitions.len()
);
let answer = if dst_state == self.dst.accepting {
// truncation: `size_of(Src) >= size_of(Dst)`
//
// Why is truncation OK to do? Because even though the Src is bigger, all we care about
// is whether we have enough data for the Dst to be valid in accordance with what its
// type dictates.
// For example, in a u8 to `()` transmutation, we have enough data available from the u8
// to transmute it to a `()` (though in this case does `()` really need any data to
// begin with? It doesn't). Same thing with u8 to fieldless struct.
// Now then, why is something like u8 to bool not allowed? That is not because the bool
// is smaller in size, but rather because those 2 bits that we are re-interpreting from
// the u8 could introduce invalid states for the bool type.
//
// So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee
// that none of the actually-used data can introduce an invalid state for Dst's type, we
// are able to safely transmute, even with truncation.
Answer::Yes
} else if src_state == self.src.accepting {
// extension: `size_of(Src) >= size_of(Dst)`
@ -214,108 +235,201 @@ where
Answer::No(Reason::DstIsTooBig)
}
} else {
let src_quantification = if self.assume.validity {
let src_quantifier = if self.assume.validity {
// if the compiler may assume that the programmer is doing additional validity checks,
// (e.g.: that `src != 3u8` when the destination type is `bool`)
// then there must exist at least one transition out of `src_state` such that the transmute is viable...
there_exists
Quantifier::ThereExists
} else {
// if the compiler cannot assume that the programmer is doing additional validity checks,
// then for all transitions out of `src_state`, such that the transmute is viable...
// then there must exist at least one transition out of `src_state` such that the transmute is viable...
for_all
// then there must exist at least one transition out of `dst_state` such that the transmute is viable...
Quantifier::ForAll
};
src_quantification(
self.src.bytes_from(src_state).unwrap_or(&Map::default()),
|(&src_validity, &src_state_prime)| {
if let Some(dst_state_prime) = self.dst.byte_from(dst_state, src_validity) {
self.answer_memo(cache, src_state_prime, dst_state_prime)
} else if let Some(dst_state_prime) =
self.dst.byte_from(dst_state, Byte::Uninit)
{
self.answer_memo(cache, src_state_prime, dst_state_prime)
} else {
Answer::No(Reason::DstIsBitIncompatible)
}
},
)
let bytes_answer = src_quantifier.apply(
// for each of the byte transitions out of the `src_state`...
self.src.bytes_from(src_state).unwrap_or(&Map::default()).into_iter().map(
|(&src_validity, &src_state_prime)| {
// ...try to find a matching transition out of `dst_state`.
if let Some(dst_state_prime) =
self.dst.byte_from(dst_state, src_validity)
{
self.answer_memo(cache, src_state_prime, dst_state_prime)
} else if let Some(dst_state_prime) =
// otherwise, see if `dst_state` has any outgoing `Uninit` transitions
// (any init byte is a valid uninit byte)
self.dst.byte_from(dst_state, Byte::Uninit)
{
self.answer_memo(cache, src_state_prime, dst_state_prime)
} else {
// otherwise, we've exhausted our options.
// the DFAs, from this point onwards, are bit-incompatible.
Answer::No(Reason::DstIsBitIncompatible)
}
},
),
);
// The below early returns reflect how this code would behave:
// if self.assume.validity {
// or(bytes_answer, refs_answer)
// } else {
// and(bytes_answer, refs_answer)
// }
// ...if `refs_answer` was computed lazily. The below early
// returns can be deleted without impacting the correctness of
// the algoritm; only its performance.
debug!(?bytes_answer);
match bytes_answer {
Answer::No(_) if !self.assume.validity => return bytes_answer,
Answer::Yes if self.assume.validity => return bytes_answer,
_ => {}
};
let refs_answer = src_quantifier.apply(
// for each reference transition out of `src_state`...
self.src.refs_from(src_state).unwrap_or(&Map::default()).into_iter().map(
|(&src_ref, &src_state_prime)| {
// ...there exists a reference transition out of `dst_state`...
Quantifier::ThereExists.apply(
self.dst
.refs_from(dst_state)
.unwrap_or(&Map::default())
.into_iter()
.map(|(&dst_ref, &dst_state_prime)| {
if !src_ref.is_mutable() && dst_ref.is_mutable() {
Answer::No(Reason::DstIsMoreUnique)
} else if !self.assume.alignment
&& src_ref.min_align() < dst_ref.min_align()
{
Answer::No(Reason::DstHasStricterAlignment {
src_min_align: src_ref.min_align(),
dst_min_align: dst_ref.min_align(),
})
} else {
// ...such that `src` is transmutable into `dst`, if
// `src_ref` is transmutability into `dst_ref`.
and(
Answer::If(Condition::IfTransmutable {
src: src_ref,
dst: dst_ref,
}),
self.answer_memo(
cache,
src_state_prime,
dst_state_prime,
),
)
}
}),
)
},
),
);
if self.assume.validity {
or(bytes_answer, refs_answer)
} else {
and(bytes_answer, refs_answer)
}
};
cache.insert((src_state, dst_state), answer.clone());
if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) {
panic!("failed to correctly cache transmutability")
}
answer
}
}
}
impl<R> Answer<R>
fn and<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
where
R: layout::Ref,
R: PartialEq,
{
pub(crate) fn and(self, rhs: Self) -> Self {
match (self, rhs) {
(Self::No(reason), _) | (_, Self::No(reason)) => Self::No(reason),
(Self::Yes, Self::Yes) => Self::Yes,
(Self::IfAll(mut lhs), Self::IfAll(ref mut rhs)) => {
lhs.append(rhs);
Self::IfAll(lhs)
}
(constraint, Self::IfAll(mut constraints))
| (Self::IfAll(mut constraints), constraint) => {
constraints.push(constraint);
Self::IfAll(constraints)
}
(lhs, rhs) => Self::IfAll(vec![lhs, rhs]),
match (lhs, rhs) {
// If both are errors, then we should return the more specific one
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
| (Answer::No(reason), Answer::No(_))
// If either is an error, return it
| (Answer::No(reason), _) | (_, Answer::No(reason)) => Answer::No(reason),
// If only one side has a condition, pass it along
| (Answer::Yes, other) | (other, Answer::Yes) => other,
// If both sides have IfAll conditions, merge them
(Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => {
lhs.append(rhs);
Answer::If(Condition::IfAll(lhs))
}
}
pub(crate) fn or(self, rhs: Self) -> Self {
match (self, rhs) {
(Self::Yes, _) | (_, Self::Yes) => Self::Yes,
(Self::No(lhr), Self::No(rhr)) => Self::No(lhr),
(Self::IfAny(mut lhs), Self::IfAny(ref mut rhs)) => {
lhs.append(rhs);
Self::IfAny(lhs)
}
(constraint, Self::IfAny(mut constraints))
| (Self::IfAny(mut constraints), constraint) => {
constraints.push(constraint);
Self::IfAny(constraints)
}
(lhs, rhs) => Self::IfAny(vec![lhs, rhs]),
// If only one side is an IfAll, add the other Condition to it
(Answer::If(cond), Answer::If(Condition::IfAll(mut conds)))
| (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => {
conds.push(cond);
Answer::If(Condition::IfAll(conds))
}
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAll
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])),
}
}
pub fn for_all<R, I, F>(iter: I, f: F) -> Answer<R>
fn or<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
where
R: layout::Ref,
I: IntoIterator,
F: FnMut(<I as IntoIterator>::Item) -> Answer<R>,
R: PartialEq,
{
use std::ops::ControlFlow::{Break, Continue};
let (Continue(result) | Break(result)) =
iter.into_iter().map(f).try_fold(Answer::Yes, |constraints, constraint| {
match constraint.and(constraints) {
Answer::No(reason) => Break(Answer::No(reason)),
maybe => Continue(maybe),
}
});
result
match (lhs, rhs) {
// If both are errors, then we should return the more specific one
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
| (Answer::No(reason), Answer::No(_)) => Answer::No(reason),
// Otherwise, errors can be ignored for the rest of the pattern matching
(Answer::No(_), other) | (other, Answer::No(_)) => or(other, Answer::Yes),
// If only one side has a condition, pass it along
(Answer::Yes, other) | (other, Answer::Yes) => other,
// If both sides have IfAny conditions, merge them
(Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => {
lhs.append(rhs);
Answer::If(Condition::IfAny(lhs))
}
// If only one side is an IfAny, add the other Condition to it
(Answer::If(cond), Answer::If(Condition::IfAny(mut conds)))
| (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => {
conds.push(cond);
Answer::If(Condition::IfAny(conds))
}
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAny
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])),
}
}
pub fn there_exists<R, I, F>(iter: I, f: F) -> Answer<R>
where
R: layout::Ref,
I: IntoIterator,
F: FnMut(<I as IntoIterator>::Item) -> Answer<R>,
{
use std::ops::ControlFlow::{Break, Continue};
let (Continue(result) | Break(result)) = iter.into_iter().map(f).try_fold(
Answer::No(Reason::DstIsBitIncompatible),
|constraints, constraint| match constraint.or(constraints) {
Answer::Yes => Break(Answer::Yes),
maybe => Continue(maybe),
},
);
result
pub enum Quantifier {
ThereExists,
ForAll,
}
impl Quantifier {
pub fn apply<R, I>(&self, iter: I) -> Answer<R>
where
R: layout::Ref,
I: IntoIterator<Item = Answer<R>>,
{
use std::ops::ControlFlow::{Break, Continue};
let (init, try_fold_f): (_, fn(_, _) -> _) = match self {
Self::ThereExists => {
(Answer::No(Reason::DstIsBitIncompatible), |accum: Answer<R>, next| {
match or(accum, next) {
Answer::Yes => Break(Answer::Yes),
maybe => Continue(maybe),
}
})
}
Self::ForAll => (Answer::Yes, |accum: Answer<R>, next| {
let answer = and(accum, next);
match answer {
Answer::No(_) => Break(answer),
maybe => Continue(maybe),
}
}),
};
let (Continue(result) | Break(result)) = iter.into_iter().try_fold(init, try_fold_f);
result
}
}

View File

@ -1,9 +1,11 @@
use super::query_context::test::{Def, UltraMinimal};
use crate::maybe_transmutable::MaybeTransmutableQuery;
use crate::{layout, Answer, Reason};
use crate::{layout, Reason};
use itertools::Itertools;
mod bool {
use crate::Answer;
use super::*;
#[test]

View File

@ -0,0 +1,23 @@
// check-fail
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: false,
lifetimes: true,
safety: true,
validity: true,
}
}>
{}
}
fn main() {
assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
}

View File

@ -0,0 +1,30 @@
error[E0277]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` in the defining scope of `assert::Context`
--> $DIR/align-fail.rs:22:55
|
LL | ...tatic [u8; 0], &'static [u16; 0]>();
| ^^^^^^^^^^^^^^^^^ The minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)
|
note: required by a bound in `is_maybe_transmutable`
--> $DIR/align-fail.rs:10:14
|
LL | pub fn is_maybe_transmutable<Src, Dst>()
| --------------------- required by a bound in this function
LL | where
LL | Dst: BikeshedIntrinsicFrom<Src, Context, {
| ______________^
LL | | Assume {
LL | | alignment: false,
LL | | lifetimes: true,
... |
LL | | }
LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`
help: consider removing the leading `&`-reference
|
LL - assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>();
LL + assert::is_maybe_transmutable::<&'static [u8; 0], [u16; 0]>();
|
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View File

@ -0,0 +1,23 @@
// check-pass
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: false,
lifetimes: false,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
assert::is_maybe_transmutable::<&'static [u16; 0], &'static [u8; 0]>();
}

View File

@ -90,7 +90,7 @@ error[E0277]: `u8` cannot be safely transmuted into `V0i16` in the defining scop
--> $DIR/primitive_reprs_should_have_correct_length.rs:72:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u8` isn't a bit-valid value of `V0i16`
| ^^^^^^^ The size of `u8` is smaller than the size of `V0i16`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -134,7 +134,7 @@ error[E0277]: `u8` cannot be safely transmuted into `V0u16` in the defining scop
--> $DIR/primitive_reprs_should_have_correct_length.rs:80:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u8` isn't a bit-valid value of `V0u16`
| ^^^^^^^ The size of `u8` is smaller than the size of `V0u16`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -178,7 +178,7 @@ error[E0277]: `u16` cannot be safely transmuted into `V0i32` in the defining sco
--> $DIR/primitive_reprs_should_have_correct_length.rs:96:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u16` isn't a bit-valid value of `V0i32`
| ^^^^^^^ The size of `u16` is smaller than the size of `V0i32`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -222,7 +222,7 @@ error[E0277]: `u16` cannot be safely transmuted into `V0u32` in the defining sco
--> $DIR/primitive_reprs_should_have_correct_length.rs:104:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u16` isn't a bit-valid value of `V0u32`
| ^^^^^^^ The size of `u16` is smaller than the size of `V0u32`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -266,7 +266,7 @@ error[E0277]: `u32` cannot be safely transmuted into `V0i64` in the defining sco
--> $DIR/primitive_reprs_should_have_correct_length.rs:120:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u32` isn't a bit-valid value of `V0i64`
| ^^^^^^^ The size of `u32` is smaller than the size of `V0i64`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -310,7 +310,7 @@ error[E0277]: `u32` cannot be safely transmuted into `V0u64` in the defining sco
--> $DIR/primitive_reprs_should_have_correct_length.rs:128:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u32` isn't a bit-valid value of `V0u64`
| ^^^^^^^ The size of `u32` is smaller than the size of `V0u64`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -354,7 +354,7 @@ error[E0277]: `u8` cannot be safely transmuted into `V0isize` in the defining sc
--> $DIR/primitive_reprs_should_have_correct_length.rs:144:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u8` isn't a bit-valid value of `V0isize`
| ^^^^^^^ The size of `u8` is smaller than the size of `V0isize`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
@ -398,7 +398,7 @@ error[E0277]: `u8` cannot be safely transmuted into `V0usize` in the defining sc
--> $DIR/primitive_reprs_should_have_correct_length.rs:152:44
|
LL | assert::is_transmutable::<Smaller, Current, Context>();
| ^^^^^^^ At least one value of `u8` isn't a bit-valid value of `V0usize`
| ^^^^^^^ The size of `u8` is smaller than the size of `V0usize`
|
note: required by a bound in `is_transmutable`
--> $DIR/primitive_reprs_should_have_correct_length.rs:12:14

View File

@ -18,5 +18,5 @@ fn should_gracefully_handle_unknown_dst_field() {
struct Context;
#[repr(C)] struct Src;
#[repr(C)] struct Dst(Missing); //~ cannot find type
assert::is_transmutable::<Src, Dst, Context>();
assert::is_transmutable::<Src, Dst, Context>(); //~ ERROR cannot be safely transmuted
}

View File

@ -4,6 +4,22 @@ error[E0412]: cannot find type `Missing` in this scope
LL | #[repr(C)] struct Dst(Missing);
| ^^^^^^^ not found in this scope
error: aborting due to previous error
error[E0277]: `Src` cannot be safely transmuted into `Dst` in the defining scope of `should_gracefully_handle_unknown_dst_field::Context`
--> $DIR/unknown_src_field.rs:21:36
|
LL | assert::is_transmutable::<Src, Dst, Context>();
| ^^^ `Dst` has an unknown layout
|
note: required by a bound in `is_transmutable`
--> $DIR/unknown_src_field.rs:13:14
|
LL | pub fn is_transmutable<Src, Dst, Context>()
| --------------- required by a bound in this function
LL | where
LL | Dst: BikeshedIntrinsicFrom<Src, Context>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_transmutable`
For more information about this error, try `rustc --explain E0412`.
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0277, E0412.
For more information about an error, try `rustc --explain E0277`.

View File

@ -0,0 +1,17 @@
// check-fail
//[next] compile-flags: -Ztrait-solver=next
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, { Assume::SAFETY }>
{}
}
fn main() {
assert::is_transmutable::<&'static mut bool, &'static mut u8>() //~ ERROR cannot be safely transmuted
}

View File

@ -0,0 +1,18 @@
error[E0277]: `u8` cannot be safely transmuted into `bool` in the defining scope of `assert::Context`
--> $DIR/bool-mut.rs:16:50
|
LL | assert::is_transmutable::<&'static mut bool, &'static mut u8>()
| ^^^^^^^^^^^^^^^ At least one value of `u8` isn't a bit-valid value of `bool`
|
note: required by a bound in `is_transmutable`
--> $DIR/bool-mut.rs:11:14
|
LL | pub fn is_transmutable<Src, Dst>()
| --------------- required by a bound in this function
LL | where
LL | Dst: BikeshedIntrinsicFrom<Src, Context, { Assume::SAFETY }>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_transmutable`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View File

@ -1,11 +1,11 @@
error[E0277]: `u8` cannot be safely transmuted into `bool` in the defining scope of `assert::Context`
--> $DIR/bool.rs:24:35
--> $DIR/bool.rs:21:35
|
LL | assert::is_transmutable::<u8, bool>();
| ^^^^ At least one value of `u8` isn't a bit-valid value of `bool`
|
note: required by a bound in `is_transmutable`
--> $DIR/bool.rs:14:14
--> $DIR/bool.rs:11:14
|
LL | pub fn is_transmutable<Src, Dst>()
| --------------- required by a bound in this function

View File

@ -1,11 +1,11 @@
error[E0277]: `u8` cannot be safely transmuted into `bool` in the defining scope of `assert::Context`
--> $DIR/bool.rs:24:35
--> $DIR/bool.rs:21:35
|
LL | assert::is_transmutable::<u8, bool>();
| ^^^^ At least one value of `u8` isn't a bit-valid value of `bool`
|
note: required by a bound in `is_transmutable`
--> $DIR/bool.rs:14:14
--> $DIR/bool.rs:11:14
|
LL | pub fn is_transmutable<Src, Dst>()
| --------------- required by a bound in this function

View File

@ -1,10 +1,7 @@
// revisions: current next
//[next] compile-flags: -Ztrait-solver=next
#![crate_type = "lib"]
#![feature(transmutability)]
#![allow(dead_code)]
#![allow(incomplete_features)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
@ -20,7 +17,7 @@ mod assert {
{}
}
fn contrast_with_u8() {
fn main() {
assert::is_transmutable::<u8, bool>(); //~ ERROR cannot be safely transmuted
assert::is_maybe_transmutable::<u8, bool>();
assert::is_transmutable::<bool, u8>();

View File

@ -0,0 +1,25 @@
// check-fail
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: true,
lifetimes: false,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
#[repr(C)] struct A(bool, &'static A);
#[repr(C)] struct B(u8, &'static B);
assert::is_maybe_transmutable::<&'static A, &'static mut B>(); //~ ERROR cannot be safely transmuted
}

View File

@ -0,0 +1,25 @@
error[E0277]: `&A` cannot be safely transmuted into `&mut B` in the defining scope of `assert::Context`
--> $DIR/recursive-wrapper-types-bit-compatible-mut.rs:24:49
|
LL | assert::is_maybe_transmutable::<&'static A, &'static mut B>();
| ^^^^^^^^^^^^^^ `&A` is a shared reference, but `&mut B` is a unique reference
|
note: required by a bound in `is_maybe_transmutable`
--> $DIR/recursive-wrapper-types-bit-compatible-mut.rs:10:14
|
LL | pub fn is_maybe_transmutable<Src, Dst>()
| --------------------- required by a bound in this function
LL | where
LL | Dst: BikeshedIntrinsicFrom<Src, Context, {
| ______________^
LL | | Assume {
LL | | alignment: true,
LL | | lifetimes: false,
... |
LL | | }
LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View File

@ -0,0 +1,26 @@
// check-fail
// FIXME(bryangarza): Change to check-pass when coinduction is supported for BikeshedIntrinsicFrom
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: true,
lifetimes: false,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
#[repr(C)] struct A(bool, &'static A);
#[repr(C)] struct B(u8, &'static B);
assert::is_maybe_transmutable::<&'static A, &'static B>(); //~ ERROR overflow evaluating the requirement
}

View File

@ -0,0 +1,25 @@
error[E0275]: overflow evaluating the requirement `B: BikeshedIntrinsicFrom<A, assert::Context, Assume { alignment: true, lifetimes: false, safety: true, validity: false }>`
--> $DIR/recursive-wrapper-types-bit-compatible.rs:25:5
|
LL | assert::is_maybe_transmutable::<&'static A, &'static B>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: required by a bound in `is_maybe_transmutable`
--> $DIR/recursive-wrapper-types-bit-compatible.rs:11:14
|
LL | pub fn is_maybe_transmutable<Src, Dst>()
| --------------------- required by a bound in this function
LL | where
LL | Dst: BikeshedIntrinsicFrom<Src, Context, {
| ______________^
LL | | Assume {
LL | | alignment: true,
LL | | lifetimes: false,
... |
LL | | }
LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0275`.

View File

@ -0,0 +1,25 @@
// check-fail
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: true,
lifetimes: false,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
#[repr(C)] struct A(bool, &'static A);
#[repr(C)] struct B(u8, &'static B);
assert::is_maybe_transmutable::<&'static B, &'static A>(); //~ ERROR cannot be safely transmuted
}

View File

@ -0,0 +1,25 @@
error[E0277]: `B` cannot be safely transmuted into `A` in the defining scope of `assert::Context`
--> $DIR/recursive-wrapper-types-bit-incompatible.rs:24:49
|
LL | assert::is_maybe_transmutable::<&'static B, &'static A>();
| ^^^^^^^^^^ At least one value of `B` isn't a bit-valid value of `A`
|
note: required by a bound in `is_maybe_transmutable`
--> $DIR/recursive-wrapper-types-bit-incompatible.rs:10:14
|
LL | pub fn is_maybe_transmutable<Src, Dst>()
| --------------------- required by a bound in this function
LL | where
LL | Dst: BikeshedIntrinsicFrom<Src, Context, {
| ______________^
LL | | Assume {
LL | | alignment: true,
LL | | lifetimes: false,
... |
LL | | }
LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View File

@ -0,0 +1,27 @@
// check-fail
// FIXME(bryangarza): Change to check-pass when coinduction is supported for BikeshedIntrinsicFrom
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: true,
lifetimes: false,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
#[repr(C)] struct A(&'static B);
#[repr(C)] struct B(&'static A);
assert::is_maybe_transmutable::<&'static A, &'static B>(); //~ overflow evaluating the requirement
assert::is_maybe_transmutable::<&'static B, &'static A>();
}

View File

@ -1,11 +1,11 @@
error[E0277]: `&Unit` cannot be safely transmuted into `&Unit` in the defining scope of `assert::Context`
--> $DIR/references.rs:29:52
error[E0275]: overflow evaluating the requirement `A: BikeshedIntrinsicFrom<B, assert::Context, Assume { alignment: true, lifetimes: false, safety: true, validity: false }>`
--> $DIR/recursive-wrapper-types.rs:25:5
|
LL | assert::is_maybe_transmutable::<&'static Unit, &'static Unit>();
| ^^^^^^^^^^^^^ `&Unit` does not have a well-specified layout
LL | assert::is_maybe_transmutable::<&'static A, &'static B>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: required by a bound in `is_maybe_transmutable`
--> $DIR/references.rs:16:14
--> $DIR/recursive-wrapper-types.rs:11:14
|
LL | pub fn is_maybe_transmutable<Src, Dst>()
| --------------------- required by a bound in this function
@ -14,7 +14,7 @@ LL | Dst: BikeshedIntrinsicFrom<Src, Context, {
| ______________^
LL | | Assume {
LL | | alignment: true,
LL | | lifetimes: true,
LL | | lifetimes: false,
... |
LL | | }
LL | | }>
@ -22,4 +22,4 @@ LL | | }>
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
For more information about this error, try `rustc --explain E0275`.

View File

@ -0,0 +1,24 @@
// check-pass
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: false,
lifetimes: true,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
#[repr(C)] struct Unit;
assert::is_maybe_transmutable::<&'static u8, &'static Unit>();
}

View File

@ -1,11 +1,5 @@
// revisions: current next
//[next] compile-flags: -Ztrait-solver=next
//! Transmutations involving references are not yet supported.
#![crate_type = "lib"]
// check-pass
#![feature(transmutability)]
#![allow(dead_code, incomplete_features, non_camel_case_types)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
@ -16,15 +10,15 @@ mod assert {
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: true,
lifetimes: true,
lifetimes: false,
safety: true,
validity: true,
validity: false,
}
}>
{}
}
fn not_yet_implemented() {
fn main() {
#[repr(C)] struct Unit;
assert::is_maybe_transmutable::<&'static Unit, &'static Unit>(); //~ ERROR cannot be safely transmuted
assert::is_maybe_transmutable::<&'static Unit, &'static Unit>();
}

View File

@ -0,0 +1,24 @@
// check-fail
#![feature(transmutability)]
mod assert {
use std::mem::{Assume, BikeshedIntrinsicFrom};
pub struct Context;
pub fn is_maybe_transmutable<Src, Dst>()
where
Dst: BikeshedIntrinsicFrom<Src, Context, {
Assume {
alignment: true,
lifetimes: true,
safety: true,
validity: false,
}
}>
{}
}
fn main() {
#[repr(C)] struct Unit;
assert::is_maybe_transmutable::<&'static Unit, &'static u8>(); //~ ERROR cannot be safely transmuted
}

View File

@ -1,11 +1,11 @@
error[E0277]: `&Unit` cannot be safely transmuted into `&Unit` in the defining scope of `assert::Context`
--> $DIR/references.rs:29:52
error[E0277]: `Unit` cannot be safely transmuted into `u8` in the defining scope of `assert::Context`
--> $DIR/unit-to-u8.rs:23:52
|
LL | assert::is_maybe_transmutable::<&'static Unit, &'static Unit>();
| ^^^^^^^^^^^^^ `&Unit` does not have a well-specified layout
LL | assert::is_maybe_transmutable::<&'static Unit, &'static u8>();
| ^^^^^^^^^^^ The size of `Unit` is smaller than the size of `u8`
|
note: required by a bound in `is_maybe_transmutable`
--> $DIR/references.rs:16:14
--> $DIR/unit-to-u8.rs:10:14
|
LL | pub fn is_maybe_transmutable<Src, Dst>()
| --------------------- required by a bound in this function

View File

@ -2,7 +2,7 @@ error[E0277]: `()` cannot be safely transmuted into `W<'_>` in the defining scop
--> $DIR/region-infer.rs:20:5
|
LL | test();
| ^^^^ `W<'_>` does not have a well-specified layout
| ^^^^ The size of `()` is smaller than the size of `W<'_>`
|
note: required by a bound in `test`
--> $DIR/region-infer.rs:11:12