Improve safe transmute error reporting

This patch updates the error reporting when Safe Transmute is not
possible between 2 types by including the reason.

Also, fix some small bugs that occur when computing the `Answer` for
transmutability.
This commit is contained in:
Bryan Garza 2023-04-06 01:58:53 +00:00
parent 59a05ad118
commit 36febe1f4d
29 changed files with 495 additions and 593 deletions

View file

@ -663,6 +663,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
return;
}
let trait_ref = trait_predicate.to_poly_trait_ref();
let (post_message, pre_message, type_def) = self
.get_parent_trait_ref(obligation.cause.code())
.map(|(t, s)| {
@ -702,33 +703,45 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
(message, note, append_const_msg)
};
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0277,
"{}",
message
.and_then(|cannot_do_this| {
match (predicate_is_const, append_const_msg) {
// do nothing if predicate is not const
(false, _) => Some(cannot_do_this),
// suggested using default post message
(true, Some(None)) => {
Some(format!("{cannot_do_this} in const contexts"))
}
// overridden post message
(true, Some(Some(post_message))) => {
Some(format!("{cannot_do_this}{post_message}"))
}
// fallback to generic message
(true, None) => None,
let err_msg = message
.and_then(|cannot_do_this| {
match (predicate_is_const, append_const_msg) {
// do nothing if predicate is not const
(false, _) => Some(cannot_do_this),
// suggested using default post message
(true, Some(None)) => {
Some(format!("{cannot_do_this} in const contexts"))
}
})
.unwrap_or_else(|| format!(
// overridden post message
(true, Some(Some(post_message))) => {
Some(format!("{cannot_do_this}{post_message}"))
}
// fallback to generic message
(true, None) => None,
}
})
.unwrap_or_else(|| {
format!(
"the trait bound `{}` is not satisfied{}",
trait_predicate, post_message,
))
);
)
});
let (err_msg, safe_transmute_explanation) = if Some(trait_ref.def_id())
== 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(
trait_predicate,
obligation.clone(),
trait_ref,
span,
)
} else {
(err_msg, None)
};
let mut err = struct_span_err!(self.tcx.sess, span, E0277, "{}", err_msg);
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
err.span_label(
@ -818,6 +831,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
// at the type param with a label to suggest constraining it.
err.help(&explanation);
}
} else if let Some(custom_explanation) = safe_transmute_explanation {
err.span_label(span, custom_explanation);
} else {
err.span_label(span, explanation);
}
@ -1601,6 +1616,14 @@ trait InferCtxtPrivExt<'tcx> {
obligated_types: &mut Vec<Ty<'tcx>>,
cause_code: &ObligationCauseCode<'tcx>,
) -> bool;
fn get_safe_transmute_error_and_reason(
&self,
trait_predicate: ty::Binder<'tcx, ty::TraitPredicate<'tcx>>,
obligation: Obligation<'tcx, ty::Predicate<'tcx>>,
trait_ref: ty::Binder<'tcx, ty::TraitRef<'tcx>>,
span: Span,
) -> (String, Option<String>);
}
impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
@ -2879,6 +2902,63 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
}
false
}
fn get_safe_transmute_error_and_reason(
&self,
trait_predicate: ty::Binder<'tcx, ty::TraitPredicate<'tcx>>,
obligation: Obligation<'tcx, ty::Predicate<'tcx>>,
trait_ref: ty::Binder<'tcx, ty::TraitRef<'tcx>>,
span: Span,
) -> (String, Option<String>) {
let src_and_dst = trait_predicate.map_bound(|p| rustc_transmute::Types {
dst: p.trait_ref.substs.type_at(0),
src: p.trait_ref.substs.type_at(1),
});
let scope = trait_ref.skip_binder().substs.type_at(2);
let Some(assume) =
rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, trait_ref.skip_binder().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) => {
let dst = trait_ref.skip_binder().substs.type_at(0);
let src = trait_ref.skip_binder().substs.type_at(1);
let custom_err_msg = format!("`{src}` cannot be safely transmuted into `{dst}` in the defining scope of `{scope}`").to_string();
let reason_msg = match reason {
rustc_transmute::Reason::SrcIsUnspecified => {
format!("`{src}` does not have a well-specified layout").to_string()
}
rustc_transmute::Reason::DstIsUnspecified => {
format!("`{dst}` does not have a well-specified layout").to_string()
}
rustc_transmute::Reason::DstIsBitIncompatible => {
format!("At least one value of `{src}` isn't a bit-valid value of `{dst}`")
.to_string()
}
rustc_transmute::Reason::DstIsPrivate => format!(
"`{dst}` is or contains a type or field that is not visible in that scope"
)
.to_string(),
// 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}`")
}
};
(custom_err_msg, Some(reason_msg))
}
// 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!(
span,
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
),
_ => span_bug!(span, "Unsupported rustc_transmute::Reason variant"),
}
}
}
/// Crude way of getting back an `Expr` from a `Span`.