Fix transmute goal

This commit is contained in:
Michael Goulet 2024-09-27 14:24:31 -04:00
parent 38bbcc001e
commit 9453d2cfeb
9 changed files with 236 additions and 117 deletions

View File

@ -295,6 +295,37 @@ where
Ok(ty) Ok(ty)
} }
} }
/// Normalize a const for when it is structurally matched on, or more likely
/// when it needs `.try_to_*` called on it (e.g. to turn it into a usize).
///
/// This function is necessary in nearly all cases before matching on a const.
/// Not doing so is likely to be incomplete and therefore unsound during
/// coherence.
#[instrument(level = "trace", skip(self, param_env), ret)]
fn structurally_normalize_const(
&mut self,
param_env: I::ParamEnv,
ct: I::Const,
) -> Result<I::Const, NoSolution> {
if let ty::ConstKind::Unevaluated(..) = ct.kind() {
let normalized_ct = self.next_const_infer();
let alias_relate_goal = Goal::new(
self.cx(),
param_env,
ty::PredicateKind::AliasRelate(
ct.into(),
normalized_ct.into(),
ty::AliasRelationDirection::Equate,
),
);
self.add_goal(GoalSource::Misc, alias_relate_goal);
self.try_evaluate_added_goals()?;
Ok(self.resolve_vars_if_possible(normalized_ct))
} else {
Ok(ct)
}
}
} }
fn response_no_constraints_raw<I: Interner>( fn response_no_constraints_raw<I: Interner>(

View File

@ -627,11 +627,16 @@ where
} }
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
let assume = ecx.structurally_normalize_const(
goal.param_env,
goal.predicate.trait_ref.args.const_at(2),
)?;
let certainty = ecx.is_transmutable( let certainty = ecx.is_transmutable(
goal.param_env, goal.param_env,
goal.predicate.trait_ref.args.type_at(0), goal.predicate.trait_ref.args.type_at(0),
goal.predicate.trait_ref.args.type_at(1), goal.predicate.trait_ref.args.type_at(1),
goal.predicate.trait_ref.args.const_at(2), assume,
)?; )?;
ecx.evaluate_added_goals_and_make_canonical_response(certainty) ecx.evaluate_added_goals_and_make_canonical_response(certainty)
}) })

View File

@ -2247,124 +2247,143 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
span: Span, span: Span,
) -> GetSafeTransmuteErrorAndReason { ) -> GetSafeTransmuteErrorAndReason {
use rustc_transmute::Answer; use rustc_transmute::Answer;
self.probe(|_| {
// We don't assemble a transmutability candidate for types that are generic
// and we should have ambiguity for types that still have non-region infer.
if obligation.predicate.has_non_region_param() || obligation.has_non_region_infer() {
return GetSafeTransmuteErrorAndReason::Default;
}
// We don't assemble a transmutability candidate for types that are generic // Erase regions because layout code doesn't particularly care about regions.
// and we should have ambiguity for types that still have non-region infer. let trait_ref =
if obligation.predicate.has_non_region_param() || obligation.has_non_region_infer() { self.tcx.erase_regions(self.tcx.instantiate_bound_regions_with_erased(trait_ref));
return GetSafeTransmuteErrorAndReason::Default;
}
// Erase regions because layout code doesn't particularly care about regions. let src_and_dst = rustc_transmute::Types {
let trait_ref = dst: trait_ref.args.type_at(0),
self.tcx.erase_regions(self.tcx.instantiate_bound_regions_with_erased(trait_ref)); src: trait_ref.args.type_at(1),
};
let src_and_dst = rustc_transmute::Types { let ocx = ObligationCtxt::new(self);
dst: trait_ref.args.type_at(0), let Ok(assume) = ocx.structurally_normalize_const(
src: trait_ref.args.type_at(1), &obligation.cause,
}; obligation.param_env,
let Some(assume) = rustc_transmute::Assume::from_const( trait_ref.args.const_at(2),
self.infcx.tcx, ) else {
obligation.param_env, self.dcx().span_delayed_bug(
trait_ref.args.const_at(2), span,
) else { "Unable to construct rustc_transmute::Assume where it was previously possible",
self.dcx().span_delayed_bug( );
span, return GetSafeTransmuteErrorAndReason::Silent;
"Unable to construct rustc_transmute::Assume where it was previously possible", };
);
return GetSafeTransmuteErrorAndReason::Silent;
};
let dst = trait_ref.args.type_at(0); let Some(assume) =
let src = trait_ref.args.type_at(1); rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, assume)
else {
self.dcx().span_delayed_bug(
span,
"Unable to construct rustc_transmute::Assume where it was previously possible",
);
return GetSafeTransmuteErrorAndReason::Silent;
};
let err_msg = format!("`{src}` cannot be safely transmuted into `{dst}`"); let dst = trait_ref.args.type_at(0);
let src = trait_ref.args.type_at(1);
let err_msg = format!("`{src}` cannot be safely transmuted into `{dst}`");
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable( match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
obligation.cause, obligation.cause,
src_and_dst, src_and_dst,
assume, assume,
) { ) {
Answer::No(reason) => { Answer::No(reason) => {
let safe_transmute_explanation = match reason { let safe_transmute_explanation = match reason {
rustc_transmute::Reason::SrcIsNotYetSupported => { rustc_transmute::Reason::SrcIsNotYetSupported => {
format!("analyzing the transmutability of `{src}` is not yet supported") format!("analyzing the transmutability of `{src}` is not yet supported")
} }
rustc_transmute::Reason::DstIsNotYetSupported => { rustc_transmute::Reason::DstIsNotYetSupported => {
format!("analyzing the transmutability of `{dst}` is not yet supported") format!("analyzing the transmutability of `{dst}` is not yet supported")
} }
rustc_transmute::Reason::DstIsBitIncompatible => { rustc_transmute::Reason::DstIsBitIncompatible => {
format!("at least one value of `{src}` isn't a bit-valid value of `{dst}`") format!(
} "at least one value of `{src}` isn't a bit-valid value of `{dst}`"
)
}
rustc_transmute::Reason::DstUninhabited => { rustc_transmute::Reason::DstUninhabited => {
format!("`{dst}` is uninhabited") format!("`{dst}` is uninhabited")
} }
rustc_transmute::Reason::DstMayHaveSafetyInvariants => { rustc_transmute::Reason::DstMayHaveSafetyInvariants => {
format!("`{dst}` may carry safety invariants") format!("`{dst}` may carry safety invariants")
}
rustc_transmute::Reason::DstIsTooBig => {
format!("the size of `{src}` is smaller than the size of `{dst}`")
}
rustc_transmute::Reason::DstRefIsTooBig { src, dst } => {
let src_size = src.size;
let dst_size = dst.size;
format!(
"the referent size of `{src}` ({src_size} bytes) \
is smaller than that of `{dst}` ({dst_size} bytes)"
)
}
rustc_transmute::Reason::SrcSizeOverflow => {
format!(
"values of the type `{src}` are too big for the target architecture"
)
}
rustc_transmute::Reason::DstSizeOverflow => {
format!(
"values of the type `{dst}` are too big for the target architecture"
)
}
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")
}
};
GetSafeTransmuteErrorAndReason::Error {
err_msg,
safe_transmute_explanation: Some(safe_transmute_explanation),
} }
rustc_transmute::Reason::DstIsTooBig => {
format!("the size of `{src}` is smaller than the size of `{dst}`")
}
rustc_transmute::Reason::DstRefIsTooBig { src, dst } => {
let src_size = src.size;
let dst_size = dst.size;
format!(
"the referent size of `{src}` ({src_size} bytes) is smaller than that of `{dst}` ({dst_size} bytes)"
)
}
rustc_transmute::Reason::SrcSizeOverflow => {
format!(
"values of the type `{src}` are too big for the target architecture"
)
}
rustc_transmute::Reason::DstSizeOverflow => {
format!(
"values of the type `{dst}` are too big for the target architecture"
)
}
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")
}
};
GetSafeTransmuteErrorAndReason::Error {
err_msg,
safe_transmute_explanation: Some(safe_transmute_explanation),
} }
// Should never get a Yes at this point! We already ran it before, and did not get a Yes.
Answer::Yes => span_bug!(
span,
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
),
// Reached when a different obligation (namely `Freeze`) causes the
// transmutability analysis to fail. In this case, silence the
// transmutability error message in favor of that more specific
// error.
Answer::If(_) => GetSafeTransmuteErrorAndReason::Error {
err_msg,
safe_transmute_explanation: None,
},
} }
// Should never get a Yes at this point! We already ran it before, and did not get a Yes. })
Answer::Yes => span_bug!(
span,
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
),
// Reached when a different obligation (namely `Freeze`) causes the
// transmutability analysis to fail. In this case, silence the
// transmutability error message in favor of that more specific
// error.
Answer::If(_) => {
GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation: None }
}
}
} }
/// For effects predicates such as `<u32 as Add>::Effects: Compat<host>`, pretend that the /// For effects predicates such as `<u32 as Add>::Effects: Compat<host>`, pretend that the

View File

@ -329,4 +329,15 @@ where
.at(cause, param_env) .at(cause, param_env)
.structurally_normalize(value, &mut **self.engine.borrow_mut()) .structurally_normalize(value, &mut **self.engine.borrow_mut())
} }
pub fn structurally_normalize_const(
&self,
cause: &ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
value: ty::Const<'tcx>,
) -> Result<ty::Const<'tcx>, Vec<E>> {
self.infcx
.at(cause, param_env)
.structurally_normalize_const(value, &mut **self.engine.borrow_mut())
}
} }

View File

@ -405,11 +405,14 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let predicate = obligation.predicate.skip_binder(); let predicate = obligation.predicate.skip_binder();
let Some(assume) = rustc_transmute::Assume::from_const( let mut assume = predicate.trait_ref.args.const_at(2);
self.infcx.tcx, // FIXME(min_generic_const_exprs): We should shallowly normalize this.
obligation.param_env, if self.tcx().features().generic_const_exprs {
predicate.trait_ref.args.const_at(2), assume = assume.normalize_internal(self.tcx(), obligation.param_env);
) else { }
let Some(assume) =
rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, assume)
else {
return Err(Unimplemented); return Err(Unimplemented);
}; };

View File

@ -82,6 +82,8 @@ impl<'tcx> At<'_, 'tcx> {
} }
Ok(self.infcx.resolve_vars_if_possible(new_infer_ct)) Ok(self.infcx.resolve_vars_if_possible(new_infer_ct))
} else if self.infcx.tcx.features().generic_const_exprs {
Ok(ct.normalize_internal(self.infcx.tcx, self.param_env))
} else { } else {
Ok(self.normalize(ct).into_value_registering_obligations(self.infcx, fulfill_cx)) Ok(self.normalize(ct).into_value_registering_obligations(self.infcx, fulfill_cx))
} }

View File

@ -134,12 +134,7 @@ mod rustc {
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
let Some((cv, ty)) = c.try_to_valtree() else { let Some((cv, ty)) = c.try_to_valtree() else {
return Some(Self { return None;
alignment: true,
lifetimes: true,
safety: true,
validity: true,
});
}; };
let adt_def = ty.ty_adt_def()?; let adt_def = ty.ty_adt_def()?;

View File

@ -0,0 +1,19 @@
#![feature(transmutability)]
#![feature(generic_const_exprs)]
//~^ WARN the feature `generic_const_exprs` is incomplete
use std::mem::{Assume, TransmuteFrom};
pub fn is_transmutable<const ASSUME_ALIGNMENT: bool>()
where
(): TransmuteFrom<(), { Assume::SAFETY }>,
{
}
fn foo<const N: usize>() {
is_transmutable::<{}>();
//~^ ERROR the trait bound `(): TransmuteFrom<(), { Assume::SAFETY }>` is not satisfied
//~| ERROR mismatched types
}
fn main() {}

View File

@ -0,0 +1,34 @@
warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/dont-assume-err-is-yes-issue-126377.rs:2:12
|
LL | #![feature(generic_const_exprs)]
| ^^^^^^^^^^^^^^^^^^^
|
= note: see issue #76560 <https://github.com/rust-lang/rust/issues/76560> for more information
= note: `#[warn(incomplete_features)]` on by default
error[E0308]: mismatched types
--> $DIR/dont-assume-err-is-yes-issue-126377.rs:14:23
|
LL | is_transmutable::<{}>();
| ^^ expected `bool`, found `()`
error[E0277]: the trait bound `(): TransmuteFrom<(), { Assume::SAFETY }>` is not satisfied
--> $DIR/dont-assume-err-is-yes-issue-126377.rs:14:23
|
LL | is_transmutable::<{}>();
| ^^ the trait `TransmuteFrom<(), { Assume::SAFETY }>` is not implemented for `()`
|
note: required by a bound in `is_transmutable`
--> $DIR/dont-assume-err-is-yes-issue-126377.rs:9:9
|
LL | pub fn is_transmutable<const ASSUME_ALIGNMENT: bool>()
| --------------- required by a bound in this function
LL | where
LL | (): TransmuteFrom<(), { Assume::SAFETY }>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_transmutable`
error: aborting due to 2 previous errors; 1 warning emitted
Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.