about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt.rs8
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs41
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/confirmation.rs35
-rw-r--r--compiler/rustc_transmute/src/layout/tree.rs4
-rw-r--r--compiler/rustc_transmute/src/lib.rs20
-rw-r--r--compiler/rustc_transmute/src/maybe_transmutable/mod.rs152
-rw-r--r--compiler/rustc_transmute/src/maybe_transmutable/tests.rs12
-rw-r--r--tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.rs2
-rw-r--r--tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr20
9 files changed, 167 insertions, 127 deletions
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<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(),
@@ -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<String>);
+    ) -> 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<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));
 
@@ -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<ImplSourceBuiltinData<PredicateObligation<'tcx>>, 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<rustc_transmute::layout::rustc::Ref<'tcx>>,
+            cond: Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
         ) -> Vec<PredicateObligation<'tcx>> {
-            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<LayoutError<'tcx>> 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<R> = Result<Option<Condition<R>>, 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<R> {
+    Yes,
+    No(Reason),
+    If(Condition<R>),
+}
 
-/// 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<R> {
     /// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`.
@@ -35,7 +41,7 @@ pub enum Condition<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.
@@ -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<F, M>(
         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<<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);
-
-                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)),
+            let Self { src, dst, scope, assume, context } = self;
+
+            // 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<<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);
@@ -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<<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))));
 
@@ -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<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
 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<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
 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<R>, next| {
-                match or(accum, next) {
-                    Ok(None) => Break(Ok(None)),
+            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),
                 }
             }),
-            Self::ForAll => (Ok(None), |accum: Answer<R>, 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::<Src, Dst, Context>();
+    assert::is_transmutable::<Src, Dst, Context>(); //~ 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::<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`
+
+error: aborting due to 2 previous errors
 
-For more information about this error, try `rustc --explain E0412`.
+Some errors have detailed explanations: E0277, E0412.
+For more information about an error, try `rustc --explain E0277`.