Auto merge of #130950 - compiler-errors:yeet-eval, r=BoxyUwU
Continue to get rid of `ty::Const::{try_}eval*` This PR mostly does: * Removes all of the `try_eval_*` and `eval_*` helpers from `ty::Const`, and replace their usages with `try_to_*`. * Remove `ty::Const::eval`. * Rename `ty::Const::normalize` to `ty::Const::normalize_internal`. This function is still used in the normalization code itself. * Fix some weirdness around the `TransmuteFrom` goal. I'm happy to split it out further; for example, I could probably land the first part which removes the helpers, or the changes to codegen which are more obvious than the changes to tools. r? BoxyUwU Part of https://github.com/rust-lang/rust/issues/130704
This commit is contained in:
commit
f2ba41113d
@ -187,10 +187,10 @@ fn do_mir_borrowck<'tcx>(
|
||||
|
||||
let location_table = LocationTable::new(body);
|
||||
|
||||
let move_data = MoveData::gather_moves(body, tcx, param_env, |_| true);
|
||||
let move_data = MoveData::gather_moves(body, tcx, |_| true);
|
||||
let promoted_move_data = promoted
|
||||
.iter_enumerated()
|
||||
.map(|(idx, body)| (idx, MoveData::gather_moves(body, tcx, param_env, |_| true)));
|
||||
.map(|(idx, body)| (idx, MoveData::gather_moves(body, tcx, |_| true)));
|
||||
|
||||
let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
|
||||
.into_engine(tcx, body)
|
||||
|
@ -1128,7 +1128,6 @@ fn relate_type_and_user_type(
|
||||
}
|
||||
let projected_ty = curr_projected_ty.projection_ty_core(
|
||||
tcx,
|
||||
self.param_env,
|
||||
proj,
|
||||
|this, field, ()| {
|
||||
let ty = this.field_ty(tcx, field);
|
||||
@ -1919,7 +1918,7 @@ fn check_rvalue(&mut self, body: &Body<'tcx>, rvalue: &Rvalue<'tcx>, location: L
|
||||
// than 1.
|
||||
// If the length is larger than 1, the repeat expression will need to copy the
|
||||
// element, so we require the `Copy` trait.
|
||||
if len.try_eval_target_usize(tcx, self.param_env).map_or(true, |len| len > 1) {
|
||||
if len.try_to_target_usize(tcx).is_none_or(|len| len > 1) {
|
||||
match operand {
|
||||
Operand::Copy(..) | Operand::Constant(..) => {
|
||||
// These are always okay: direct use of a const, or a value that can evidently be copied.
|
||||
|
@ -133,6 +133,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>(
|
||||
.expect_const()
|
||||
.try_to_valtree()
|
||||
.expect("expected monomorphic const in codegen")
|
||||
.0
|
||||
.unwrap_branch();
|
||||
|
||||
assert_eq!(x.layout(), y.layout());
|
||||
@ -806,8 +807,10 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>(
|
||||
ty::Uint(i) if i.bit_width() == Some(expected_int_bits) => m.load_scalar(fx),
|
||||
ty::Array(elem, len)
|
||||
if matches!(elem.kind(), ty::Uint(ty::UintTy::U8))
|
||||
&& len.try_eval_target_usize(fx.tcx, ty::ParamEnv::reveal_all())
|
||||
== Some(expected_bytes) =>
|
||||
&& len
|
||||
.try_to_target_usize(fx.tcx)
|
||||
.expect("expected monomorphic const in codegen")
|
||||
== expected_bytes =>
|
||||
{
|
||||
m.force_stack(fx).0.load(
|
||||
fx,
|
||||
@ -907,8 +910,10 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>(
|
||||
ty::Uint(i) if i.bit_width() == Some(expected_int_bits) => {}
|
||||
ty::Array(elem, len)
|
||||
if matches!(elem.kind(), ty::Uint(ty::UintTy::U8))
|
||||
&& len.try_eval_target_usize(fx.tcx, ty::ParamEnv::reveal_all())
|
||||
== Some(expected_bytes) => {}
|
||||
&& len
|
||||
.try_to_target_usize(fx.tcx)
|
||||
.expect("expected monomorphic const in codegen")
|
||||
== expected_bytes => {}
|
||||
_ => {
|
||||
fx.tcx.dcx().span_fatal(
|
||||
span,
|
||||
|
@ -76,8 +76,10 @@ macro_rules! require_simd {
|
||||
ty::Uint(i) if i.bit_width() == Some(expected_int_bits) => args[0].immediate(),
|
||||
ty::Array(elem, len)
|
||||
if matches!(*elem.kind(), ty::Uint(ty::UintTy::U8))
|
||||
&& len.try_eval_target_usize(bx.tcx, ty::ParamEnv::reveal_all())
|
||||
== Some(expected_bytes) =>
|
||||
&& len
|
||||
.try_to_target_usize(bx.tcx)
|
||||
.expect("expected monomorphic const in codegen")
|
||||
== expected_bytes =>
|
||||
{
|
||||
let place = PlaceRef::alloca(bx, args[0].layout);
|
||||
args[0].val.store(bx, place);
|
||||
@ -696,8 +698,10 @@ macro_rules! arith_binary {
|
||||
}
|
||||
ty::Array(elem, len)
|
||||
if matches!(*elem.kind(), ty::Uint(ty::UintTy::U8))
|
||||
&& len.try_eval_target_usize(bx.tcx, ty::ParamEnv::reveal_all())
|
||||
== Some(expected_bytes) =>
|
||||
&& len
|
||||
.try_to_target_usize(bx.tcx)
|
||||
.expect("expected monomorphic const in codegen")
|
||||
== expected_bytes =>
|
||||
{
|
||||
// Zero-extend iN to the array length:
|
||||
let ze = bx.zext(result, bx.type_ix(expected_bytes * 8));
|
||||
|
@ -1179,8 +1179,10 @@ macro_rules! require_simd {
|
||||
ty::Uint(i) if i.bit_width() == Some(expected_int_bits) => args[0].immediate(),
|
||||
ty::Array(elem, len)
|
||||
if matches!(elem.kind(), ty::Uint(ty::UintTy::U8))
|
||||
&& len.try_eval_target_usize(bx.tcx, ty::ParamEnv::reveal_all())
|
||||
== Some(expected_bytes) =>
|
||||
&& len
|
||||
.try_to_target_usize(bx.tcx)
|
||||
.expect("expected monomorphic const in codegen")
|
||||
== expected_bytes =>
|
||||
{
|
||||
let place = PlaceRef::alloca(bx, args[0].layout);
|
||||
args[0].val.store(bx, place);
|
||||
@ -1245,12 +1247,7 @@ macro_rules! require_simd {
|
||||
}
|
||||
|
||||
if name == sym::simd_shuffle_generic {
|
||||
let idx = fn_args[2]
|
||||
.expect_const()
|
||||
.eval(tcx, ty::ParamEnv::reveal_all(), span)
|
||||
.unwrap()
|
||||
.1
|
||||
.unwrap_branch();
|
||||
let idx = fn_args[2].expect_const().try_to_valtree().unwrap().0.unwrap_branch();
|
||||
let n = idx.len() as u64;
|
||||
|
||||
let (out_len, out_ty) = require_simd!(ret_ty, SimdReturn);
|
||||
@ -1469,8 +1466,10 @@ macro_rules! require_simd {
|
||||
}
|
||||
ty::Array(elem, len)
|
||||
if matches!(elem.kind(), ty::Uint(ty::UintTy::U8))
|
||||
&& len.try_eval_target_usize(bx.tcx, ty::ParamEnv::reveal_all())
|
||||
== Some(expected_bytes) =>
|
||||
&& len
|
||||
.try_to_target_usize(bx.tcx)
|
||||
.expect("expected monomorphic const in codegen")
|
||||
== expected_bytes =>
|
||||
{
|
||||
// Zero-extend iN to the array length:
|
||||
let ze = bx.zext(i_, bx.type_ix(expected_bytes * 8));
|
||||
|
@ -23,7 +23,6 @@
|
||||
use rustc_middle::ty::{
|
||||
self, ExistentialProjection, GenericArgKind, GenericArgsRef, ParamEnv, Ty, TyCtxt,
|
||||
};
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_target::abi::Integer;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@ -685,21 +684,25 @@ fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut S
|
||||
ty::ConstKind::Param(param) => {
|
||||
write!(output, "{}", param.name)
|
||||
}
|
||||
ty::ConstKind::Value(ty, _) => {
|
||||
ty::ConstKind::Value(ty, valtree) => {
|
||||
match ty.kind() {
|
||||
ty::Int(ity) => {
|
||||
// FIXME: directly extract the bits from a valtree instead of evaluating an
|
||||
// already evaluated `Const` in order to get the bits.
|
||||
let bits = ct.eval_bits(tcx, ty::ParamEnv::reveal_all());
|
||||
let bits = ct
|
||||
.try_to_bits(tcx, ty::ParamEnv::reveal_all())
|
||||
.expect("expected monomorphic const in codegen");
|
||||
let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128;
|
||||
write!(output, "{val}")
|
||||
}
|
||||
ty::Uint(_) => {
|
||||
let val = ct.eval_bits(tcx, ty::ParamEnv::reveal_all());
|
||||
let val = ct
|
||||
.try_to_bits(tcx, ty::ParamEnv::reveal_all())
|
||||
.expect("expected monomorphic const in codegen");
|
||||
write!(output, "{val}")
|
||||
}
|
||||
ty::Bool => {
|
||||
let val = ct.try_eval_bool(tcx, ty::ParamEnv::reveal_all()).unwrap();
|
||||
let val = ct.try_to_bool().expect("expected monomorphic const in codegen");
|
||||
write!(output, "{val}")
|
||||
}
|
||||
_ => {
|
||||
@ -711,8 +714,9 @@ fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut S
|
||||
// avoiding collisions and will make the emitted type names shorter.
|
||||
let hash_short = tcx.with_stable_hashing_context(|mut hcx| {
|
||||
let mut hasher = StableHasher::new();
|
||||
let ct = ct.eval(tcx, ty::ParamEnv::reveal_all(), DUMMY_SP).unwrap();
|
||||
hcx.while_hashing_spans(false, |hcx| ct.hash_stable(hcx, &mut hasher));
|
||||
hcx.while_hashing_spans(false, |hcx| {
|
||||
(ty, valtree).hash_stable(hcx, &mut hasher)
|
||||
});
|
||||
hasher.finish::<Hash64>()
|
||||
});
|
||||
|
||||
|
@ -43,7 +43,7 @@ pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
|
||||
|
||||
// We go to `usize` as we cannot allocate anything bigger anyway.
|
||||
let (field_count, variant, down) = match ty.kind() {
|
||||
ty::Array(_, len) => (len.eval_target_usize(tcx.tcx, param_env) as usize, None, op),
|
||||
ty::Array(_, len) => (len.try_to_target_usize(tcx.tcx)? as usize, None, op),
|
||||
ty::Adt(def, _) if def.variants().is_empty() => {
|
||||
return None;
|
||||
}
|
||||
|
@ -391,7 +391,9 @@ fn unsize_into_ptr(
|
||||
let ptr = self.read_pointer(src)?;
|
||||
let val = Immediate::new_slice(
|
||||
ptr,
|
||||
length.eval_target_usize(*self.tcx, self.param_env),
|
||||
length
|
||||
.try_to_target_usize(*self.tcx)
|
||||
.expect("expected monomorphic const in const eval"),
|
||||
self,
|
||||
);
|
||||
self.write_immediate(val, dest)
|
||||
|
@ -1037,7 +1037,11 @@ fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(len) = len_const.try_eval_target_usize(tcx, tcx.param_env(def.did())) {
|
||||
// FIXME(repr_simd): This check is nice, but perhaps unnecessary due to the fact
|
||||
// we do not expect users to implement their own `repr(simd)` types. If they could,
|
||||
// this check is easily side-steppable by hiding the const behind normalization.
|
||||
// The consequence is that the error is, in general, only observable post-mono.
|
||||
if let Some(len) = len_const.try_to_target_usize(tcx) {
|
||||
if len == 0 {
|
||||
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
|
||||
return;
|
||||
|
@ -76,9 +76,7 @@ fn get_asm_ty(&self, ty: Ty<'tcx>) -> Option<InlineAsmType> {
|
||||
|
||||
let (size, ty) = match elem_ty.kind() {
|
||||
ty::Array(ty, len) => {
|
||||
if let Some(len) =
|
||||
len.try_eval_target_usize(self.tcx, self.tcx.param_env(adt.did()))
|
||||
{
|
||||
if let Some(len) = len.try_to_target_usize(self.tcx) {
|
||||
(len, *ty)
|
||||
} else {
|
||||
return None;
|
||||
|
@ -1487,7 +1487,7 @@ pub(crate) fn try_structurally_resolve_const(
|
||||
}
|
||||
}
|
||||
} else if self.tcx.features().generic_const_exprs {
|
||||
ct.normalize(self.tcx, self.param_env)
|
||||
ct.normalize_internal(self.tcx, self.param_env)
|
||||
} else {
|
||||
ct
|
||||
}
|
||||
|
@ -2601,7 +2601,7 @@ fn ty_find_init_error<'tcx>(
|
||||
ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field, init))
|
||||
}
|
||||
Array(ty, len) => {
|
||||
if matches!(len.try_eval_target_usize(cx.tcx, cx.param_env), Some(v) if v > 0) {
|
||||
if matches!(len.try_to_target_usize(cx.tcx), Some(v) if v > 0) {
|
||||
// Array length known at array non-empty -- recurse.
|
||||
ty_find_init_error(cx, *ty, init)
|
||||
} else {
|
||||
|
@ -346,7 +346,7 @@ fn is_ty_must_use<'tcx>(
|
||||
None
|
||||
}
|
||||
}
|
||||
ty::Array(ty, len) => match len.try_eval_target_usize(cx.tcx, cx.param_env) {
|
||||
ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) {
|
||||
// If the array is empty we don't lint, to avoid false positives
|
||||
Some(0) | None => None,
|
||||
// If the array is definitely non-empty, we can do `#[must_use]` checking.
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use either::Either;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_macros::{HashStable, Lift, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
|
||||
use rustc_session::RemapFileNameExt;
|
||||
@ -320,8 +321,14 @@ pub fn eval(
|
||||
Const::Ty(_, c) => {
|
||||
// We want to consistently have a "clean" value for type system constants (i.e., no
|
||||
// data hidden in the padding), so we always go through a valtree here.
|
||||
let (ty, val) = c.eval(tcx, param_env, span)?;
|
||||
Ok(tcx.valtree_to_const_val((ty, val)))
|
||||
match c.eval_valtree(tcx, param_env, span) {
|
||||
Ok((ty, val)) => Ok(tcx.valtree_to_const_val((ty, val))),
|
||||
Err(Either::Left(_bad_ty)) => Err(tcx
|
||||
.dcx()
|
||||
.delayed_bug("`mir::Const::eval` called on a non-valtree-compatible type")
|
||||
.into()),
|
||||
Err(Either::Right(e)) => Err(e),
|
||||
}
|
||||
}
|
||||
Const::Unevaluated(uneval, _) => {
|
||||
// FIXME: We might want to have a `try_eval`-like function on `Unevaluated`
|
||||
|
@ -55,7 +55,7 @@ pub fn field_ty(self, tcx: TyCtxt<'tcx>, f: FieldIdx) -> Ty<'tcx> {
|
||||
/// `PlaceElem`, where we can just use the `Ty` that is already
|
||||
/// stored inline on field projection elems.
|
||||
pub fn projection_ty(self, tcx: TyCtxt<'tcx>, elem: PlaceElem<'tcx>) -> PlaceTy<'tcx> {
|
||||
self.projection_ty_core(tcx, ty::ParamEnv::empty(), &elem, |_, _, ty| ty, |_, ty| ty)
|
||||
self.projection_ty_core(tcx, &elem, |_, _, ty| ty, |_, ty| ty)
|
||||
}
|
||||
|
||||
/// `place_ty.projection_ty_core(tcx, elem, |...| { ... })`
|
||||
@ -66,7 +66,6 @@ pub fn projection_ty(self, tcx: TyCtxt<'tcx>, elem: PlaceElem<'tcx>) -> PlaceTy<
|
||||
pub fn projection_ty_core<V, T>(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
elem: &ProjectionElem<V, T>,
|
||||
mut handle_field: impl FnMut(&Self, FieldIdx, T) -> Ty<'tcx>,
|
||||
mut handle_opaque_cast_and_subtype: impl FnMut(&Self, T) -> Ty<'tcx>,
|
||||
@ -93,7 +92,9 @@ pub fn projection_ty_core<V, T>(
|
||||
ty::Slice(..) => self.ty,
|
||||
ty::Array(inner, _) if !from_end => Ty::new_array(tcx, *inner, to - from),
|
||||
ty::Array(inner, size) if from_end => {
|
||||
let size = size.eval_target_usize(tcx, param_env);
|
||||
let size = size
|
||||
.try_to_target_usize(tcx)
|
||||
.expect("expected subslice projection on fixed-size array");
|
||||
let len = size - from - to;
|
||||
Ty::new_array(tcx, *inner, len)
|
||||
}
|
||||
|
@ -398,133 +398,65 @@ pub fn eval_valtree(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the evaluated constant
|
||||
#[inline]
|
||||
pub fn eval(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
span: Span,
|
||||
) -> Result<(Ty<'tcx>, ValTree<'tcx>), ErrorHandled> {
|
||||
self.eval_valtree(tcx, param_env, span).map_err(|err| {
|
||||
match err {
|
||||
Either::Right(err) => err,
|
||||
Either::Left(_bad_ty) => {
|
||||
// This can happen when we run on ill-typed code.
|
||||
let e = tcx.dcx().span_delayed_bug(
|
||||
span,
|
||||
"`ty::Const::eval` called on a non-valtree-compatible type",
|
||||
);
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Normalizes the constant to a value or an error if possible.
|
||||
#[inline]
|
||||
pub fn normalize(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Self {
|
||||
match self.eval(tcx, param_env, DUMMY_SP) {
|
||||
pub fn normalize_internal(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Self {
|
||||
match self.eval_valtree(tcx, param_env, DUMMY_SP) {
|
||||
Ok((ty, val)) => Self::new_value(tcx, val, ty),
|
||||
Err(ErrorHandled::Reported(r, _span)) => Self::new_error(tcx, r.into()),
|
||||
Err(ErrorHandled::TooGeneric(_span)) => self,
|
||||
Err(Either::Left(_bad_ty)) => {
|
||||
// This can happen when we run on ill-typed code.
|
||||
Self::new_error(
|
||||
tcx,
|
||||
tcx.dcx()
|
||||
.delayed_bug("`ty::Const::eval` called on a non-valtree-compatible type"),
|
||||
)
|
||||
}
|
||||
Err(Either::Right(ErrorHandled::Reported(r, _span))) => Self::new_error(tcx, r.into()),
|
||||
Err(Either::Right(ErrorHandled::TooGeneric(_span))) => self,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_eval_scalar(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
) -> Option<(Ty<'tcx>, Scalar)> {
|
||||
let (ty, val) = self.eval(tcx, param_env, DUMMY_SP).ok()?;
|
||||
let val = val.try_to_scalar()?;
|
||||
Some((ty, val))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Attempts to evaluate the given constant to bits. Can fail to evaluate in the presence of
|
||||
/// generics (or erroneous code) or if the value can't be represented as bits (e.g. because it
|
||||
/// contains const generic parameters or pointers).
|
||||
pub fn try_eval_scalar_int(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
) -> Option<(Ty<'tcx>, ScalarInt)> {
|
||||
let (ty, scalar) = self.try_eval_scalar(tcx, param_env)?;
|
||||
let val = scalar.try_to_scalar_int().ok()?;
|
||||
Some((ty, val))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Attempts to evaluate the given constant to bits. Can fail to evaluate in the presence of
|
||||
/// generics (or erroneous code) or if the value can't be represented as bits (e.g. because it
|
||||
/// contains const generic parameters or pointers).
|
||||
pub fn try_eval_bits(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option<u128> {
|
||||
let (ty, scalar) = self.try_eval_scalar_int(tcx, param_env)?;
|
||||
let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size;
|
||||
// if `ty` does not depend on generic parameters, use an empty param_env
|
||||
Some(scalar.to_bits(size))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Panics if the value cannot be evaluated or doesn't contain a valid integer of the given type.
|
||||
pub fn eval_bits(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> u128 {
|
||||
self.try_eval_bits(tcx, param_env)
|
||||
.unwrap_or_else(|| bug!("failed to evalate {:#?} to bits", self))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_eval_target_usize(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
) -> Option<u64> {
|
||||
let (_, scalar) = self.try_eval_scalar_int(tcx, param_env)?;
|
||||
Some(scalar.to_target_usize(tcx))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_eval_bool(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option<bool> {
|
||||
let (_, scalar) = self.try_eval_scalar_int(tcx, param_env)?;
|
||||
scalar.try_into().ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Panics if the value cannot be evaluated or doesn't contain a valid `usize`.
|
||||
pub fn eval_target_usize(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> u64 {
|
||||
self.try_eval_target_usize(tcx, param_env)
|
||||
.unwrap_or_else(|| bug!("expected usize, got {:#?}", self))
|
||||
}
|
||||
|
||||
/// Panics if self.kind != ty::ConstKind::Value
|
||||
pub fn to_valtree(self) -> ty::ValTree<'tcx> {
|
||||
pub fn to_valtree(self) -> (ty::ValTree<'tcx>, Ty<'tcx>) {
|
||||
match self.kind() {
|
||||
ty::ConstKind::Value(_, valtree) => valtree,
|
||||
ty::ConstKind::Value(ty, valtree) => (valtree, ty),
|
||||
_ => bug!("expected ConstKind::Value, got {:?}", self.kind()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to convert to a `ValTree`
|
||||
pub fn try_to_valtree(self) -> Option<ty::ValTree<'tcx>> {
|
||||
pub fn try_to_valtree(self) -> Option<(ty::ValTree<'tcx>, Ty<'tcx>)> {
|
||||
match self.kind() {
|
||||
ty::ConstKind::Value(_, valtree) => Some(valtree),
|
||||
ty::ConstKind::Value(ty, valtree) => Some((valtree, ty)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_to_scalar(self) -> Option<Scalar> {
|
||||
self.try_to_valtree()?.try_to_scalar()
|
||||
pub fn try_to_scalar(self) -> Option<(Scalar, Ty<'tcx>)> {
|
||||
let (valtree, ty) = self.try_to_valtree()?;
|
||||
Some((valtree.try_to_scalar()?, ty))
|
||||
}
|
||||
|
||||
pub fn try_to_bool(self) -> Option<bool> {
|
||||
self.try_to_valtree()?.try_to_scalar_int()?.try_to_bool().ok()
|
||||
self.try_to_valtree()?.0.try_to_scalar_int()?.try_to_bool().ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_to_target_usize(self, tcx: TyCtxt<'tcx>) -> Option<u64> {
|
||||
self.try_to_valtree()?.try_to_target_usize(tcx)
|
||||
self.try_to_valtree()?.0.try_to_target_usize(tcx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Attempts to evaluate the given constant to bits. Can fail to evaluate in the presence of
|
||||
/// generics (or erroneous code) or if the value can't be represented as bits (e.g. because it
|
||||
/// contains const generic parameters or pointers).
|
||||
pub fn try_to_bits(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option<u128> {
|
||||
let (scalar, ty) = self.try_to_scalar()?;
|
||||
let scalar = scalar.try_to_scalar_int().ok()?;
|
||||
let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size;
|
||||
// if `ty` does not depend on generic parameters, use an empty param_env
|
||||
Some(scalar.to_bits(size))
|
||||
}
|
||||
|
||||
pub fn is_ct_infer(self) -> bool {
|
||||
|
@ -85,7 +85,7 @@ fn apply_inner<E: std::fmt::Debug>(
|
||||
match self {
|
||||
Self::False => Ok(false),
|
||||
Self::True => Ok(true),
|
||||
Self::ConstIsZero(const_) => match const_.try_eval_target_usize(tcx, param_env) {
|
||||
Self::ConstIsZero(const_) => match const_.try_to_target_usize(tcx) {
|
||||
None | Some(0) => Ok(true),
|
||||
Some(1..) => Ok(false),
|
||||
},
|
||||
|
@ -397,7 +397,7 @@ pub fn compute(
|
||||
}
|
||||
}
|
||||
ty::Array(inner, len) if tcx.features().transmute_generic_consts => {
|
||||
let len_eval = len.try_eval_target_usize(tcx, param_env);
|
||||
let len_eval = len.try_to_target_usize(tcx);
|
||||
if len_eval == Some(0) {
|
||||
return Ok(SizeSkeleton::Known(Size::from_bytes(0), None));
|
||||
}
|
||||
|
@ -1117,7 +1117,12 @@ pub fn simd_size_and_type(self, tcx: TyCtxt<'tcx>) -> (u64, Ty<'tcx>) {
|
||||
// The way we evaluate the `N` in `[T; N]` here only works since we use
|
||||
// `simd_size_and_type` post-monomorphization. It will probably start to ICE
|
||||
// if we use it in generic code. See the `simd-array-trait` ui test.
|
||||
(f0_len.eval_target_usize(tcx, ParamEnv::empty()), *f0_elem_ty)
|
||||
(
|
||||
f0_len
|
||||
.try_to_target_usize(tcx)
|
||||
.expect("expected SIMD field to have definite array size"),
|
||||
*f0_elem_ty,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -863,7 +863,7 @@ fn open_drop(&mut self) -> BasicBlock {
|
||||
ty::Adt(def, args) => self.open_drop_for_adt(*def, args),
|
||||
ty::Dynamic(..) => self.complete_drop(self.succ, self.unwind),
|
||||
ty::Array(ety, size) => {
|
||||
let size = size.try_eval_target_usize(self.tcx(), self.elaborator.param_env());
|
||||
let size = size.try_to_target_usize(self.tcx());
|
||||
self.open_drop_for_array(*ety, size)
|
||||
}
|
||||
ty::Slice(ety) => self.drop_loop_pair(*ety),
|
||||
|
@ -18,18 +18,12 @@ struct MoveDataBuilder<'a, 'tcx, F> {
|
||||
body: &'a Body<'tcx>,
|
||||
loc: Location,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
data: MoveData<'tcx>,
|
||||
filter: F,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
|
||||
fn new(
|
||||
body: &'a Body<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
filter: F,
|
||||
) -> Self {
|
||||
fn new(body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, filter: F) -> Self {
|
||||
let mut move_paths = IndexVec::new();
|
||||
let mut path_map = IndexVec::new();
|
||||
let mut init_path_map = IndexVec::new();
|
||||
@ -59,7 +53,6 @@ fn new(
|
||||
body,
|
||||
loc: Location::START,
|
||||
tcx,
|
||||
param_env,
|
||||
data: MoveData {
|
||||
moves: IndexVec::new(),
|
||||
loc_map: LocationMap::new(body),
|
||||
@ -308,10 +301,9 @@ fn finalize(self) -> MoveData<'tcx> {
|
||||
pub(super) fn gather_moves<'tcx>(
|
||||
body: &Body<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
filter: impl Fn(Ty<'tcx>) -> bool,
|
||||
) -> MoveData<'tcx> {
|
||||
let mut builder = MoveDataBuilder::new(body, tcx, param_env, filter);
|
||||
let mut builder = MoveDataBuilder::new(body, tcx, filter);
|
||||
|
||||
builder.gather_args();
|
||||
|
||||
@ -550,7 +542,9 @@ fn gather_move(&mut self, place: Place<'tcx>) {
|
||||
};
|
||||
let base_ty = base_place.ty(self.body, self.tcx).ty;
|
||||
let len: u64 = match base_ty.kind() {
|
||||
ty::Array(_, size) => size.eval_target_usize(self.tcx, self.param_env),
|
||||
ty::Array(_, size) => size
|
||||
.try_to_target_usize(self.tcx)
|
||||
.expect("expected subslice projection on fixed-size array"),
|
||||
_ => bug!("from_end: false slice pattern of non-array type"),
|
||||
};
|
||||
for offset in from..to {
|
||||
|
@ -4,7 +4,7 @@
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::{IndexSlice, IndexVec};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{Ty, TyCtxt};
|
||||
use rustc_span::Span;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@ -352,10 +352,9 @@ impl<'tcx> MoveData<'tcx> {
|
||||
pub fn gather_moves(
|
||||
body: &Body<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
filter: impl Fn(Ty<'tcx>) -> bool,
|
||||
) -> MoveData<'tcx> {
|
||||
builder::gather_moves(body, tcx, param_env, filter)
|
||||
builder::gather_moves(body, tcx, filter)
|
||||
}
|
||||
|
||||
/// For the move path `mpi`, returns the root local variable that starts the path.
|
||||
|
@ -40,8 +40,7 @@ pub fn sanity_check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
|
||||
debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
|
||||
}
|
||||
|
||||
let param_env = tcx.param_env(def_id);
|
||||
let move_data = MoveData::gather_moves(body, tcx, param_env, |_| true);
|
||||
let move_data = MoveData::gather_moves(body, tcx, |_| true);
|
||||
|
||||
if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
|
||||
let flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
|
||||
|
@ -1957,7 +1957,8 @@ fn check_must_not_suspend_ty<'tcx>(
|
||||
let descr_pre = &format!("{}array{} of ", data.descr_pre, plural_suffix);
|
||||
check_must_not_suspend_ty(tcx, ty, hir_id, param_env, SuspendCheckData {
|
||||
descr_pre,
|
||||
plural_len: len.try_eval_target_usize(tcx, param_env).unwrap_or(0) as usize + 1,
|
||||
// FIXME(must_not_suspend): This is wrong. We should handle printing unevaluated consts.
|
||||
plural_len: len.try_to_target_usize(tcx).unwrap_or(0) as usize + 1,
|
||||
..data
|
||||
})
|
||||
}
|
||||
|
@ -58,8 +58,7 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
let param_env = tcx.param_env_reveal_all_normalized(def_id);
|
||||
// For types that do not need dropping, the behaviour is trivial. So we only need to track
|
||||
// init/uninit for types that do need dropping.
|
||||
let move_data =
|
||||
MoveData::gather_moves(body, tcx, param_env, |ty| ty.needs_drop(tcx, param_env));
|
||||
let move_data = MoveData::gather_moves(body, tcx, |ty| ty.needs_drop(tcx, param_env));
|
||||
let elaborate_patch = {
|
||||
let env = MoveDataParamEnv { move_data, param_env };
|
||||
|
||||
|
@ -602,7 +602,7 @@ fn eval_rvalue(&mut self, rvalue: &Rvalue<'tcx>, dest: &Place<'tcx>) -> Option<(
|
||||
Len(place) => {
|
||||
let len = if let ty::Array(_, n) = place.ty(self.local_decls(), self.tcx).ty.kind()
|
||||
{
|
||||
n.try_eval_target_usize(self.tcx, self.param_env)?
|
||||
n.try_to_target_usize(self.tcx)?
|
||||
} else {
|
||||
match self.get_const(place)? {
|
||||
Value::Immediate(src) => src.len(&self.ecx).discard_err()?,
|
||||
|
@ -329,7 +329,7 @@ fn validate_place(&mut self, place: PlaceRef<'tcx>) -> Result<(), Unpromotable>
|
||||
// Determine the type of the thing we are indexing.
|
||||
&& let ty::Array(_, len) = place_base.ty(self.body, self.tcx).ty.kind()
|
||||
// It's an array; determine its length.
|
||||
&& let Some(len) = len.try_eval_target_usize(self.tcx, self.param_env)
|
||||
&& let Some(len) = len.try_to_target_usize(self.tcx)
|
||||
// If the index is in-bounds, go ahead.
|
||||
&& idx < len
|
||||
{
|
||||
@ -407,7 +407,7 @@ fn validate_ref(&mut self, kind: BorrowKind, place: &Place<'tcx>) -> Result<(),
|
||||
// mutably without consequences. However, only &mut []
|
||||
// is allowed right now.
|
||||
if let ty::Array(_, len) = ty.kind() {
|
||||
match len.try_eval_target_usize(self.tcx, self.param_env) {
|
||||
match len.try_to_target_usize(self.tcx) {
|
||||
Some(0) => {}
|
||||
_ => return Err(Unpromotable),
|
||||
}
|
||||
|
@ -19,8 +19,7 @@
|
||||
impl<'tcx> crate::MirPass<'tcx> for RemoveUninitDrops {
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
let param_env = tcx.param_env(body.source.def_id());
|
||||
let move_data =
|
||||
MoveData::gather_moves(body, tcx, param_env, |ty| ty.needs_drop(tcx, param_env));
|
||||
let move_data = MoveData::gather_moves(body, tcx, |ty| ty.needs_drop(tcx, param_env));
|
||||
|
||||
let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
|
||||
.into_engine(tcx, body)
|
||||
|
@ -295,6 +295,37 @@ fn structurally_normalize_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>(
|
||||
|
@ -627,11 +627,16 @@ fn consider_builtin_transmute_candidate(
|
||||
}
|
||||
|
||||
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(
|
||||
goal.param_env,
|
||||
goal.predicate.trait_ref.args.type_at(0),
|
||||
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)
|
||||
})
|
||||
|
@ -133,7 +133,9 @@ fn encode_const<'tcx>(
|
||||
// bool value false is encoded as 0 and true as 1.
|
||||
match ct_ty.kind() {
|
||||
ty::Int(ity) => {
|
||||
let bits = c.eval_bits(tcx, ty::ParamEnv::reveal_all());
|
||||
let bits = c
|
||||
.try_to_bits(tcx, ty::ParamEnv::reveal_all())
|
||||
.expect("expected monomorphic const in cfi");
|
||||
let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128;
|
||||
if val < 0 {
|
||||
s.push('n');
|
||||
@ -141,7 +143,9 @@ fn encode_const<'tcx>(
|
||||
let _ = write!(s, "{val}");
|
||||
}
|
||||
ty::Uint(_) => {
|
||||
let val = c.eval_bits(tcx, ty::ParamEnv::reveal_all());
|
||||
let val = c
|
||||
.try_to_bits(tcx, ty::ParamEnv::reveal_all())
|
||||
.expect("expected monomorphic const in cfi");
|
||||
let _ = write!(s, "{val}");
|
||||
}
|
||||
ty::Bool => {
|
||||
|
@ -406,7 +406,7 @@ fn eval_target_usize_ty(&self, cnst: &TyConst) -> Result<u64, Error> {
|
||||
let tcx = tables.tcx;
|
||||
let mir_const = cnst.internal(&mut *tables, tcx);
|
||||
mir_const
|
||||
.try_eval_target_usize(tables.tcx, ParamEnv::empty())
|
||||
.try_to_target_usize(tables.tcx)
|
||||
.ok_or_else(|| Error::new(format!("Const `{cnst:?}` cannot be encoded as u64")))
|
||||
}
|
||||
|
||||
|
@ -555,7 +555,6 @@ fn print_dyn_existential(
|
||||
|
||||
fn print_const(&mut self, ct: ty::Const<'tcx>) -> Result<(), PrintError> {
|
||||
// We only mangle a typed value if the const can be evaluated.
|
||||
let ct = ct.normalize(self.tcx, ty::ParamEnv::reveal_all());
|
||||
let (ct_ty, valtree) = match ct.kind() {
|
||||
ty::ConstKind::Value(ty, val) => (ty, val),
|
||||
|
||||
@ -592,7 +591,9 @@ fn print_const(&mut self, ct: ty::Const<'tcx>) -> Result<(), PrintError> {
|
||||
ty::Uint(_) | ty::Int(_) | ty::Bool | ty::Char => {
|
||||
ct_ty.print(self)?;
|
||||
|
||||
let mut bits = ct.eval_bits(self.tcx, ty::ParamEnv::reveal_all());
|
||||
let mut bits = ct
|
||||
.try_to_bits(self.tcx, ty::ParamEnv::reveal_all())
|
||||
.expect("expected const to be monomorphic");
|
||||
|
||||
// Negative integer values are mangled using `n` as a "sign prefix".
|
||||
if let ty::Int(ity) = ct_ty.kind() {
|
||||
|
@ -2230,7 +2230,7 @@ fn get_safe_transmute_error_and_reason(
|
||||
span: Span,
|
||||
) -> GetSafeTransmuteErrorAndReason {
|
||||
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() {
|
||||
@ -2245,8 +2245,10 @@ fn get_safe_transmute_error_and_reason(
|
||||
dst: trait_ref.args.type_at(0),
|
||||
src: trait_ref.args.type_at(1),
|
||||
};
|
||||
let Some(assume) = rustc_transmute::Assume::from_const(
|
||||
self.infcx.tcx,
|
||||
|
||||
let ocx = ObligationCtxt::new(self);
|
||||
let Ok(assume) = ocx.structurally_normalize_const(
|
||||
&obligation.cause,
|
||||
obligation.param_env,
|
||||
trait_ref.args.const_at(2),
|
||||
) else {
|
||||
@ -2257,9 +2259,18 @@ fn get_safe_transmute_error_and_reason(
|
||||
return GetSafeTransmuteErrorAndReason::Silent;
|
||||
};
|
||||
|
||||
let Some(assume) =
|
||||
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 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(
|
||||
@ -2278,7 +2289,9 @@ fn get_safe_transmute_error_and_reason(
|
||||
}
|
||||
|
||||
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 => {
|
||||
@ -2295,7 +2308,8 @@ fn get_safe_transmute_error_and_reason(
|
||||
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)"
|
||||
"the referent size of `{src}` ({src_size} bytes) \
|
||||
is smaller than that of `{dst}` ({dst_size} bytes)"
|
||||
)
|
||||
}
|
||||
rustc_transmute::Reason::SrcSizeOverflow => {
|
||||
@ -2313,11 +2327,14 @@ fn get_safe_transmute_error_and_reason(
|
||||
dst_min_align,
|
||||
} => {
|
||||
format!(
|
||||
"the minimum alignment of `{src}` ({src_min_align}) should be greater than that of `{dst}` ({dst_min_align})"
|
||||
"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")
|
||||
format!(
|
||||
"`{src}` is a shared reference, but `{dst}` is a unique reference"
|
||||
)
|
||||
}
|
||||
// Already reported by rustc
|
||||
rustc_transmute::Reason::TypeError => {
|
||||
@ -2344,10 +2361,12 @@ fn get_safe_transmute_error_and_reason(
|
||||
// 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 }
|
||||
}
|
||||
Answer::If(_) => GetSafeTransmuteErrorAndReason::Error {
|
||||
err_msg,
|
||||
safe_transmute_explanation: None,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// For effects predicates such as `<u32 as Add>::Effects: Compat<host>`, pretend that the
|
||||
|
@ -231,7 +231,7 @@ pub fn on_unimplemented_note(
|
||||
// Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]`
|
||||
if let ty::Array(aty, len) = self_ty.kind() {
|
||||
flags.push((sym::_Self, Some("[]".to_string())));
|
||||
let len = len.try_to_valtree().and_then(|v| v.try_to_target_usize(self.tcx));
|
||||
let len = len.try_to_target_usize(self.tcx);
|
||||
flags.push((sym::_Self, Some(format!("[{aty}; _]"))));
|
||||
if let Some(n) = len {
|
||||
flags.push((sym::_Self, Some(format!("[{aty}; {n}]"))));
|
||||
|
@ -329,4 +329,15 @@ pub fn structurally_normalize(
|
||||
.at(cause, param_env)
|
||||
.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())
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
|
||||
// should actually be okay since without `feature(generic_const_exprs)` the only
|
||||
// const arguments that have a non-empty param env are array repeat counts. These
|
||||
// do not appear in the type system though.
|
||||
c.normalize(self.0, ty::ParamEnv::empty())
|
||||
c.normalize_internal(self.0, ty::ParamEnv::empty())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,7 +413,7 @@ fn fold_const(&mut self, constant: ty::Const<'tcx>) -> ty::Const<'tcx> {
|
||||
self.selcx.infcx,
|
||||
&mut self.universes,
|
||||
constant,
|
||||
|constant| constant.normalize(tcx, self.param_env),
|
||||
|constant| constant.normalize_internal(tcx, self.param_env),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ fn try_fold_const(
|
||||
self.infcx,
|
||||
&mut self.universes,
|
||||
constant,
|
||||
|constant| constant.normalize(self.infcx.tcx, self.param_env),
|
||||
|constant| constant.normalize_internal(self.infcx.tcx, self.param_env),
|
||||
);
|
||||
debug!(?constant, ?self.param_env);
|
||||
constant.try_super_fold_with(self)
|
||||
|
@ -405,11 +405,14 @@ fn flatten_answer_tree<'tcx>(
|
||||
|
||||
let predicate = obligation.predicate.skip_binder();
|
||||
|
||||
let Some(assume) = rustc_transmute::Assume::from_const(
|
||||
self.infcx.tcx,
|
||||
obligation.param_env,
|
||||
predicate.trait_ref.args.const_at(2),
|
||||
) else {
|
||||
let mut assume = predicate.trait_ref.args.const_at(2);
|
||||
// FIXME(min_generic_const_exprs): We should shallowly normalize this.
|
||||
if self.tcx().features().generic_const_exprs {
|
||||
assume = assume.normalize_internal(self.tcx(), obligation.param_env);
|
||||
}
|
||||
let Some(assume) =
|
||||
rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, assume)
|
||||
else {
|
||||
return Err(Unimplemented);
|
||||
};
|
||||
|
||||
|
@ -82,6 +82,8 @@ fn structurally_normalize_const<E: 'tcx>(
|
||||
}
|
||||
|
||||
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 {
|
||||
Ok(self.normalize(ct).into_value_registering_obligations(self.infcx, fulfill_cx))
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ mod rustc {
|
||||
use rustc_macros::TypeVisitable;
|
||||
use rustc_middle::traits::ObligationCause;
|
||||
use rustc_middle::ty::{Const, ParamEnv, Ty, TyCtxt, ValTree};
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -134,13 +133,8 @@ pub fn from_const<'tcx>(
|
||||
use rustc_middle::ty::ScalarInt;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
let Ok((ty, cv)) = c.eval(tcx, param_env, DUMMY_SP) else {
|
||||
return Some(Self {
|
||||
alignment: true,
|
||||
lifetimes: true,
|
||||
safety: true,
|
||||
validity: true,
|
||||
});
|
||||
let Some((cv, ty)) = c.try_to_valtree() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let adt_def = ty.ty_adt_def()?;
|
||||
|
@ -176,12 +176,12 @@ fn layout_of_uncached<'tcx>(
|
||||
if let Abi::Scalar(scalar) | Abi::ScalarPair(scalar, _) = &mut layout.abi {
|
||||
if let Some(start) = start {
|
||||
scalar.valid_range_mut().start = start
|
||||
.try_eval_bits(tcx, param_env)
|
||||
.try_to_bits(tcx, param_env)
|
||||
.ok_or_else(|| error(cx, LayoutError::Unknown(ty)))?;
|
||||
}
|
||||
if let Some(end) = end {
|
||||
let mut end = end
|
||||
.try_eval_bits(tcx, param_env)
|
||||
.try_to_bits(tcx, param_env)
|
||||
.ok_or_else(|| error(cx, LayoutError::Unknown(ty)))?;
|
||||
if !include_end {
|
||||
end = end.wrapping_sub(1);
|
||||
@ -321,7 +321,7 @@ fn layout_of_uncached<'tcx>(
|
||||
}
|
||||
|
||||
let count = count
|
||||
.try_eval_target_usize(tcx, param_env)
|
||||
.try_to_target_usize(tcx)
|
||||
.ok_or_else(|| error(cx, LayoutError::Unknown(ty)))?;
|
||||
let element = cx.layout_of(element)?;
|
||||
let size = element
|
||||
|
@ -1817,7 +1817,7 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T
|
||||
// Only anon consts can implicitly capture params.
|
||||
// FIXME: is this correct behavior?
|
||||
let param_env = cx.tcx.param_env(*def_id);
|
||||
ct.normalize(cx.tcx, param_env)
|
||||
cx.tcx.normalize_erasing_regions(param_env, ct)
|
||||
} else {
|
||||
ct
|
||||
};
|
||||
@ -2033,8 +2033,8 @@ pub(crate) fn clean_middle_ty<'tcx>(
|
||||
Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)),
|
||||
format!("{pat:?}").into_boxed_str(),
|
||||
),
|
||||
ty::Array(ty, mut n) => {
|
||||
n = n.normalize(cx.tcx, ty::ParamEnv::reveal_all());
|
||||
ty::Array(ty, n) => {
|
||||
let n = cx.tcx.normalize_erasing_regions(cx.param_env, n);
|
||||
let n = print_const(cx, n);
|
||||
Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)), n.into())
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let Some(range) = higher::Range::hir(index) {
|
||||
// Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]
|
||||
if let ty::Array(_, s) = ty.kind() {
|
||||
let size: u128 = if let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env) {
|
||||
let size: u128 = if let Some(size) = s.try_to_target_usize(cx.tcx) {
|
||||
size.into()
|
||||
} else {
|
||||
return;
|
||||
@ -183,7 +183,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
&& let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind()
|
||||
&& *utype == ty::UintTy::Usize
|
||||
&& let ty::Array(_, s) = ty.kind()
|
||||
&& let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env)
|
||||
&& let Some(size) = s.try_to_target_usize(cx.tcx)
|
||||
{
|
||||
// get constant offset and check whether it is in bounds
|
||||
let off = usize::try_from(off).unwrap();
|
||||
|
@ -30,7 +30,7 @@ pub(super) fn check(
|
||||
return;
|
||||
}
|
||||
} else if count
|
||||
.try_eval_target_usize(cx.tcx, cx.param_env)
|
||||
.try_to_target_usize(cx.tcx)
|
||||
.map_or(true, |x| x > 32)
|
||||
&& !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN)
|
||||
{
|
||||
|
@ -472,7 +472,7 @@ fn extract_lit_value(expr: &Expr<'_>) -> Option<u128> {
|
||||
let arr_ty = cx.typeck_results().expr_ty(arr).peel_refs();
|
||||
|
||||
if let ty::Array(_, s) = arr_ty.kind() {
|
||||
let size: u128 = if let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env) {
|
||||
let size: u128 = if let Some(size) = s.try_to_target_usize(cx.tcx) {
|
||||
size.into()
|
||||
} else {
|
||||
return false;
|
||||
|
@ -207,7 +207,7 @@ fn is_end_eq_array_len<'tcx>(
|
||||
if let ExprKind::Lit(lit) = end.kind
|
||||
&& let ast::LitKind::Int(end_int, _) = lit.node
|
||||
&& let ty::Array(_, arr_len_const) = indexed_ty.kind()
|
||||
&& let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env)
|
||||
&& let Some(arr_len) = arr_len_const.try_to_target_usize(cx.tcx)
|
||||
{
|
||||
return match limits {
|
||||
ast::RangeLimits::Closed => end_int.get() + 1 >= arr_len.into(),
|
||||
|
@ -11,7 +11,7 @@
|
||||
use rustc_hir::intravisit::{Visitor, walk_pat};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, HirId, Node, Pat, PatKind, QPath, StmtKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, AdtDef, ParamEnv, TyCtxt, TypeckResults, VariantDef};
|
||||
use rustc_middle::ty::{self, AdtDef, TyCtxt, TypeckResults, VariantDef};
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
|
||||
@ -67,7 +67,6 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tc
|
||||
if v.has_enum {
|
||||
let cx = PatCtxt {
|
||||
tcx: cx.tcx,
|
||||
param_env: cx.param_env,
|
||||
typeck,
|
||||
arena: DroplessArena::default(),
|
||||
};
|
||||
@ -185,7 +184,6 @@ fn visit_pat(&mut self, pat: &'tcx Pat<'_>) -> Self::Result {
|
||||
/// The context needed to manipulate a `PatState`.
|
||||
struct PatCtxt<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
typeck: &'tcx TypeckResults<'tcx>,
|
||||
arena: DroplessArena,
|
||||
}
|
||||
@ -334,7 +332,7 @@ fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool {
|
||||
if match *cx.typeck.pat_ty(pat).peel_refs().kind() {
|
||||
ty::Adt(adt, _) => adt.is_enum() || (adt.is_struct() && !adt.non_enum_variant().fields.is_empty()),
|
||||
ty::Tuple(tys) => !tys.is_empty(),
|
||||
ty::Array(_, len) => len.try_eval_target_usize(cx.tcx, cx.param_env) != Some(1),
|
||||
ty::Array(_, len) => len.try_to_target_usize(cx.tcx) != Some(1),
|
||||
ty::Slice(..) => true,
|
||||
_ => false,
|
||||
} =>
|
||||
@ -353,7 +351,7 @@ fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool {
|
||||
},
|
||||
PatKind::Slice([sub_pat], _, []) | PatKind::Slice([], _, [sub_pat])
|
||||
if let ty::Array(_, len) = *cx.typeck.pat_ty(pat).kind()
|
||||
&& len.try_eval_target_usize(cx.tcx, cx.param_env) == Some(1) =>
|
||||
&& len.try_to_target_usize(cx.tcx) == Some(1) =>
|
||||
{
|
||||
self.add_pat(cx, sub_pat)
|
||||
},
|
||||
|
@ -31,14 +31,14 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) ->
|
||||
// parameter.
|
||||
substs
|
||||
.const_at(1)
|
||||
.try_eval_target_usize(cx.tcx, cx.param_env)
|
||||
.try_to_target_usize(cx.tcx)
|
||||
.map(u128::from)
|
||||
} else if cx.tcx.is_diagnostic_item(sym::SliceIter, did)
|
||||
&& let ExprKind::MethodCall(_, recv, ..) = iter.kind
|
||||
{
|
||||
if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() {
|
||||
// For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..)
|
||||
len.try_eval_target_usize(cx.tcx, cx.param_env).map(u128::from)
|
||||
len.try_to_target_usize(cx.tcx).map(u128::from)
|
||||
} else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) {
|
||||
match args {
|
||||
VecArgs::Vec(vec) => vec.len().try_into().ok(),
|
||||
|
@ -18,7 +18,7 @@ fn may_slice<'a>(cx: &LateContext<'a>, ty: Ty<'a>) -> bool {
|
||||
ty::Slice(_) => true,
|
||||
ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => may_slice(cx, boxed),
|
||||
ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec),
|
||||
ty::Array(_, size) => size.try_eval_target_usize(cx.tcx, cx.param_env).is_some(),
|
||||
ty::Array(_, size) => size.try_to_target_usize(cx.tcx).is_some(),
|
||||
ty::Ref(_, inner, _) => may_slice(cx, *inner),
|
||||
_ => false,
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
use clippy_utils::has_repr_attr;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{Const, FeedConstTy};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -55,16 +55,14 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
|
||||
fn is_struct_with_trailing_zero_sized_array<'tcx>(cx: &LateContext<'tcx>, item: &Item<'tcx>) -> bool {
|
||||
if let ItemKind::Struct(data, _) = &item.kind
|
||||
// First check if last field is an array
|
||||
&& let Some(last_field) = data.fields().last()
|
||||
&& let rustc_hir::TyKind::Array(_, rustc_hir::ArrayLen::Body(length)) = last_field.ty.kind
|
||||
|
||||
// Then check if that array is zero-sized
|
||||
&& let length = Const::from_const_arg(cx.tcx, length, FeedConstTy::No)
|
||||
&& let length = length.try_eval_target_usize(cx.tcx, cx.param_env)
|
||||
&& let Some(length) = length
|
||||
&& let field_ty = cx
|
||||
.tcx
|
||||
.normalize_erasing_regions(cx.param_env, cx.tcx.type_of(last_field.def_id).instantiate_identity())
|
||||
&& let ty::Array(_, array_len) = *field_ty.kind()
|
||||
&& let Some(0) = array_len.try_to_target_usize(cx.tcx)
|
||||
{
|
||||
length == 0
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ fn all_bindings_are_for_conv<'tcx>(
|
||||
tys.len() == elements.len() && tys.iter().chain(final_tys.iter().copied()).all_equal()
|
||||
},
|
||||
(ToType::Tuple, ty::Array(ty, len)) => {
|
||||
let Some(len) = len.try_eval_target_usize(cx.tcx, cx.param_env) else { return false };
|
||||
let Some(len) = len.try_to_target_usize(cx.tcx) else { return false };
|
||||
len as usize == elements.len() && final_tys.iter().chain(once(ty)).all_equal()
|
||||
},
|
||||
_ => false,
|
||||
|
@ -472,7 +472,7 @@ fn expr(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple),
|
||||
ExprKind::Repeat(value, _) => {
|
||||
let n = match self.typeck.expr_ty(e).kind() {
|
||||
ty::Array(_, n) => n.try_eval_target_usize(self.tcx, self.param_env)?,
|
||||
ty::Array(_, n) => n.try_to_target_usize(self.tcx)?,
|
||||
_ => span_bug!(e.span, "typeck error"),
|
||||
};
|
||||
self.expr(value).map(|v| Constant::Repeat(Box::new(v), n))
|
||||
@ -553,7 +553,7 @@ pub fn eval_is_empty(&self, e: &Expr<'_>) -> Option<bool> {
|
||||
ExprKind::Array(vec) => self.multi(vec).map(|v| v.is_empty()),
|
||||
ExprKind::Repeat(..) => {
|
||||
if let ty::Array(_, n) = self.typeck.expr_ty(e).kind() {
|
||||
Some(n.try_eval_target_usize(self.tcx, self.param_env)? == 0)
|
||||
Some(n.try_to_target_usize(self.tcx)? == 0)
|
||||
} else {
|
||||
span_bug!(e.span, "typeck error");
|
||||
}
|
||||
|
@ -989,7 +989,7 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
|
||||
(Ok(size), _) => size,
|
||||
(Err(_), ty::Tuple(list)) => list.iter().map(|t| approx_ty_size(cx, t)).sum(),
|
||||
(Err(_), ty::Array(t, n)) => {
|
||||
n.try_eval_target_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t)
|
||||
n.try_to_target_usize(cx.tcx).unwrap_or_default() * approx_ty_size(cx, *t)
|
||||
},
|
||||
(Err(_), ty::Adt(def, subst)) if def.is_struct() => def
|
||||
.variants()
|
||||
@ -1207,7 +1207,7 @@ pub fn interior_mut_ty_chain(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) ->
|
||||
let chain = match *ty.kind() {
|
||||
ty::RawPtr(inner_ty, _) if !self.ignore_pointers => self.interior_mut_ty_chain(cx, inner_ty),
|
||||
ty::Ref(_, inner_ty, _) | ty::Slice(inner_ty) => self.interior_mut_ty_chain(cx, inner_ty),
|
||||
ty::Array(inner_ty, size) if size.try_eval_target_usize(cx.tcx, cx.param_env) != Some(0) => {
|
||||
ty::Array(inner_ty, size) if size.try_to_target_usize(cx.tcx) != Some(0) => {
|
||||
self.interior_mut_ty_chain(cx, inner_ty)
|
||||
},
|
||||
ty::Tuple(fields) => fields.iter().find_map(|ty| self.interior_mut_ty_chain(cx, ty)),
|
||||
|
@ -1,8 +1,7 @@
|
||||
use either::Either;
|
||||
use rustc_apfloat::{Float, Round};
|
||||
use rustc_middle::ty::FloatTy;
|
||||
use rustc_middle::ty::layout::{HasParamEnv, LayoutOf};
|
||||
use rustc_middle::{mir, ty};
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::{mir, ty, ty::FloatTy};
|
||||
use rustc_span::{Symbol, sym};
|
||||
use rustc_target::abi::{Endian, HasDataLayout};
|
||||
|
||||
@ -633,9 +632,9 @@ enum Op {
|
||||
|
||||
let index = generic_args[2]
|
||||
.expect_const()
|
||||
.eval(*this.tcx, this.param_env(), this.tcx.span)
|
||||
.try_to_valtree()
|
||||
.unwrap()
|
||||
.1
|
||||
.0
|
||||
.unwrap_branch();
|
||||
let index_len = index.len();
|
||||
|
||||
|
@ -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() {}
|
@ -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`.
|
Loading…
Reference in New Issue
Block a user