diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index e8abd5964bb..fb28faa2b7c 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -668,6 +668,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { scope: Ty<'tcx>, assume: rustc_transmute::Assume, ) -> Result { + 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(), @@ -675,11 +676,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { scope, assume, ) { - Ok(None) => Ok(Certainty::Yes), - Err(_) - | Ok(Some(rustc_transmute::Condition::IfTransmutable { .. })) - | Ok(Some(rustc_transmute::Condition::IfAll(_))) - | Ok(Some(rustc_transmute::Condition::IfAny(_))) => Err(NoSolution), + Answer::Yes => Ok(Certainty::Yes), + Answer::No(_) | Answer::If(_) => Err(NoSolution), } } } diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 23126f2cb52..36d5f3566c6 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -65,6 +65,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 @@ -724,11 +729,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) }; @@ -1292,7 +1303,7 @@ trait InferCtxtPrivExt<'tcx> { obligation: PredicateObligation<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, span: Span, - ) -> (String, Option); + ) -> GetSafeTransmuteErrorAndReason; fn add_tuple_trait_message( &self, @@ -2738,7 +2749,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { obligation: PredicateObligation<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, span: Span, - ) -> (String, Option) { + ) -> 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)); @@ -2758,13 +2771,13 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { scope, assume, ) { - Err(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") } @@ -2794,11 +2807,21 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { 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. - Ok(None) => span_bug!( + Answer::Yes => span_bug!( span, "Inconsistent rustc_transmute::is_transmutable(...) result, got Yes", ), diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 3f07ad814ff..404055e1df6 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -285,28 +285,22 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { &mut self, obligation: &TraitObligation<'tcx>, ) -> Result>, SelectionError<'tcx>> { + 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>, - answer: rustc_transmute::Condition>, + cond: Condition>, ) -> Vec> { - match answer { + match cond { // FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll` // Not possible until the trait solver supports disjunctions of obligations - rustc_transmute::Condition::IfAll(answers) - | rustc_transmute::Condition::IfAny(answers) => { - let mut nested = vec![]; - for flattened in answers - .into_iter() - .map(|answer| flatten_answer_tree(tcx, obligation, predicate, answer)) - { - nested.extend(flattened); - } - nested - } - rustc_transmute::Condition::IfTransmutable { src, dst } => { + 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); @@ -333,11 +327,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // 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. - let mut obligations = vec![make_obl(src.ty, dst.ty)]; - if dst.mutability == Mutability::Mut { - obligations.push(make_obl(dst.ty, src.ty)); + 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)], } - obligations } } } @@ -370,9 +363,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ); let fully_flattened = match maybe_transmutable { - Err(_) => Err(Unimplemented)?, - Ok(Some(mt)) => flatten_answer_tree(self.tcx(), obligation, predicate, mt), - Ok(None) => vec![], + Answer::No(_) => Err(Unimplemented)?, + Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, predicate, cond), + Answer::Yes => vec![], }; debug!(?fully_flattened); diff --git a/compiler/rustc_transmute/src/layout/tree.rs b/compiler/rustc_transmute/src/layout/tree.rs index ed9309b015d..6b718be7b15 100644 --- a/compiler/rustc_transmute/src/layout/tree.rs +++ b/compiler/rustc_transmute/src/layout/tree.rs @@ -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> for Err { fn from(err: LayoutError<'tcx>) -> Self { match err { - LayoutError::Unknown(..) => Self::Unknown, + LayoutError::Unknown(..) => Self::UnknownLayout, err => unimplemented!("{:?}", err), } } diff --git a/compiler/rustc_transmute/src/lib.rs b/compiler/rustc_transmute/src/lib.rs index 7a8cbd50d45..34ad6bd8c69 100644 --- a/compiler/rustc_transmute/src/lib.rs +++ b/compiler/rustc_transmute/src/lib.rs @@ -19,10 +19,16 @@ pub struct Assume { pub validity: bool, } -/// Either we have an error, or we have an optional Condition that must hold. -pub type Answer = Result>, Reason>; +/// 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 { + Yes, + No(Reason), + If(Condition), +} -/// A condition which must hold for safe transmutation to be possible +/// A condition which must hold for safe transmutation to be possible. #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum Condition { /// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`. @@ -35,7 +41,7 @@ pub enum Condition { IfAny(Vec>), } -/// 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. @@ -52,6 +58,12 @@ pub enum Reason { 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")] diff --git a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs index 80e3489e99b..b223a90f751 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs @@ -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( self, f: F, @@ -67,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< 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 `Ok(None)` 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(Err::TypeError(_))) - | (Err(Err::Unknown), _) - | (_, Err(Err::Unknown)) => Err(Ok(None)), - (Err(Err::Unspecified), _) => Err(Err(Reason::SrcIsUnspecified)), - (_, Err(Err::Unspecified)) => Err(Err(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, } } } @@ -108,6 +105,7 @@ where #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] pub(crate) fn answer(self) -> Answer<::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); @@ -128,12 +126,13 @@ where // Convert `src` from a tree-based representation to an NFA-based representation. // If the conversion fails because `src` is uninhabited, conclude that the transmutation // is acceptable, because instances of the `src` type do not exist. - let src = Nfa::from_tree(src).map_err(|Uninhabited| Ok(None))?; + let src = Nfa::from_tree(src).map_err(|Uninhabited| Answer::Yes)?; // Convert `dst` from a tree-based representation to an NFA-based representation. // If the conversion fails because `src` is uninhabited, conclude that the transmutation // is unacceptable, because instances of the `dst` type do not exist. - let dst = Nfa::from_tree(dst).map_err(|Uninhabited| Err(Reason::DstIsPrivate))?; + let dst = + Nfa::from_tree(dst).map_err(|Uninhabited| Answer::No(Reason::DstIsPrivate))?; Ok((src, dst)) }); @@ -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<::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)))); @@ -226,13 +226,13 @@ where // 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. - Ok(None) + Answer::Yes } else if src_state == self.src.accepting { // extension: `size_of(Src) >= size_of(Dst)` if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) { self.answer_memo(cache, src_state, dst_state_prime) } else { - Err(Reason::DstIsTooBig) + Answer::No(Reason::DstIsTooBig) } } else { let src_quantifier = if self.assume.validity { @@ -265,7 +265,7 @@ where } else { // otherwise, we've exhausted our options. // the DFAs, from this point onwards, are bit-incompatible. - Err(Reason::DstIsBitIncompatible) + Answer::No(Reason::DstIsBitIncompatible) } }, ), @@ -282,8 +282,8 @@ where // the algoritm; only its performance. debug!(?bytes_answer); match bytes_answer { - Err(_) if !self.assume.validity => return bytes_answer, - Ok(None) if self.assume.validity => return bytes_answer, + Answer::No(_) if !self.assume.validity => return bytes_answer, + Answer::Yes if self.assume.validity => return bytes_answer, _ => {} }; @@ -299,11 +299,11 @@ where .into_iter() .map(|(&dst_ref, &dst_state_prime)| { if !src_ref.is_mutable() && dst_ref.is_mutable() { - Err(Reason::DstIsMoreUnique) + Answer::No(Reason::DstIsMoreUnique) } else if !self.assume.alignment && src_ref.min_align() < dst_ref.min_align() { - Err(Reason::DstHasStricterAlignment { + Answer::No(Reason::DstHasStricterAlignment { src_min_align: src_ref.min_align(), dst_min_align: dst_ref.min_align(), }) @@ -311,10 +311,10 @@ where // ...such that `src` is transmutable into `dst`, if // `src_ref` is transmutability into `dst_ref`. and( - Ok(Some(Condition::IfTransmutable { + Answer::If(Condition::IfTransmutable { src: src_ref, dst: dst_ref, - })), + }), self.answer_memo( cache, src_state_prime, @@ -346,65 +346,56 @@ fn and(lhs: Answer, rhs: Answer) -> Answer where R: PartialEq, { - // If both are errors, then we should return the more specific one - if lhs.is_err() && rhs.is_err() { - if lhs == Err(Reason::DstIsBitIncompatible) { - return rhs; - } else { - return lhs; - } - } - Ok(match (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 - (None, other) | (other, None) => other, + | (Answer::Yes, other) | (other, Answer::Yes) => other, // If both sides have IfAll conditions, merge them - (Some(Condition::IfAll(mut lhs)), Some(Condition::IfAll(ref mut rhs))) => { + (Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => { lhs.append(rhs); - Some(Condition::IfAll(lhs)) + Answer::If(Condition::IfAll(lhs)) } // If only one side is an IfAll, add the other Condition to it - (Some(cond), Some(Condition::IfAll(mut conds))) - | (Some(Condition::IfAll(mut conds)), Some(cond)) => { + (Answer::If(cond), Answer::If(Condition::IfAll(mut conds))) + | (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => { conds.push(cond); - Some(Condition::IfAll(conds)) + Answer::If(Condition::IfAll(conds)) } // Otherwise, both lhs and rhs conditions can be combined in a parent IfAll - (Some(lhs), Some(rhs)) => Some(Condition::IfAll(vec![lhs, rhs])), - }) + (Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])), + } } fn or(lhs: Answer, rhs: Answer) -> Answer where R: PartialEq, { - // If both are errors, then we should return the more specific one - if lhs.is_err() && rhs.is_err() { - if lhs == Err(Reason::DstIsBitIncompatible) { - return rhs; - } else { - return lhs; - } - } - // Otherwise, errors can be ignored for the rest of the pattern matching - let lhs = lhs.unwrap_or(None); - let rhs = rhs.unwrap_or(None); - Ok(match (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(_)) => 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 - (None, other) | (other, None) => other, + (Answer::Yes, other) | (other, Answer::Yes) => other, // If both sides have IfAny conditions, merge them - (Some(Condition::IfAny(mut lhs)), Some(Condition::IfAny(ref mut rhs))) => { + (Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => { lhs.append(rhs); - Some(Condition::IfAny(lhs)) + Answer::If(Condition::IfAny(lhs)) } // If only one side is an IfAny, add the other Condition to it - (Some(cond), Some(Condition::IfAny(mut conds))) - | (Some(Condition::IfAny(mut conds)), Some(cond)) => { + (Answer::If(cond), Answer::If(Condition::IfAny(mut conds))) + | (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => { conds.push(cond); - Some(Condition::IfAny(conds)) + Answer::If(Condition::IfAny(conds)) } // Otherwise, both lhs and rhs conditions can be combined in a parent IfAny - (Some(lhs), Some(rhs)) => Some(Condition::IfAny(vec![lhs, rhs])), - }) + (Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])), + } } pub enum Quantifier { @@ -421,16 +412,21 @@ impl Quantifier { use std::ops::ControlFlow::{Break, Continue}; let (init, try_fold_f): (_, fn(_, _) -> _) = match self { - Self::ThereExists => (Err(Reason::DstIsBitIncompatible), |accum: Answer, next| { - match or(accum, next) { - Ok(None) => Break(Ok(None)), + Self::ThereExists => { + (Answer::No(Reason::DstIsBitIncompatible), |accum: Answer, next| { + match or(accum, next) { + Answer::Yes => Break(Answer::Yes), + maybe => Continue(maybe), + } + }) + } + Self::ForAll => (Answer::Yes, |accum: Answer, next| { + let answer = and(accum, next); + match answer { + Answer::No(_) => Break(answer), maybe => Continue(maybe), } }), - Self::ForAll => (Ok(None), |accum: Answer, next| match and(accum, next) { - Err(reason) => Break(Err(reason)), - maybe => Continue(maybe), - }), }; let (Continue(result) | Break(result)) = iter.into_iter().try_fold(init, try_fold_f); diff --git a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs index df6a83df23f..e49bebf571d 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs @@ -4,6 +4,8 @@ use crate::{layout, Reason}; use itertools::Itertools; mod bool { + use crate::Answer; + use super::*; #[test] @@ -17,7 +19,7 @@ mod bool { UltraMinimal, ) .answer(); - assert_eq!(answer, Ok(None)); + assert_eq!(answer, Answer::Yes); } #[test] @@ -30,7 +32,7 @@ mod bool { UltraMinimal, ) .answer(); - assert_eq!(answer, Ok(None)); + assert_eq!(answer, Answer::Yes); } #[test] @@ -65,7 +67,7 @@ mod bool { if src_set.is_subset(&dst_set) { assert_eq!( - Ok(None), + Answer::Yes, MaybeTransmutableQuery::new( src_layout.clone(), dst_layout.clone(), @@ -80,7 +82,7 @@ mod bool { ); } else if !src_set.is_disjoint(&dst_set) { assert_eq!( - Ok(None), + Answer::Yes, MaybeTransmutableQuery::new( src_layout.clone(), dst_layout.clone(), @@ -95,7 +97,7 @@ mod bool { ); } else { assert_eq!( - Err(Reason::DstIsBitIncompatible), + Answer::No(Reason::DstIsBitIncompatible), MaybeTransmutableQuery::new( src_layout.clone(), dst_layout.clone(), diff --git a/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.rs b/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.rs index ebe34e13432..8d19cabc0f9 100644 --- a/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.rs +++ b/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.rs @@ -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::(); + assert::is_transmutable::(); //~ ERROR cannot be safely transmuted } diff --git a/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr b/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr index 475e6f429f3..c2df398b8ff 100644 --- a/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr +++ b/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr @@ -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::(); + | ^^^ `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() + | --------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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`.