Safe Transmute: Refactor error handling and Answer type
- Create `Answer` type that is not just a type alias of `Result` - Remove a usage of `map_layouts` to make the code easier to read - Don't hide errors related to Unknown Layout when computing transmutability
This commit is contained in:
parent
64a54df86f
commit
f4cf8f65a5
9 changed files with 166 additions and 126 deletions
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue