Auto merge of #94225 - matthiaskrgr:rollup-0728x8n, r=matthiaskrgr
Rollup of 10 pull requests Successful merges: - #91192 (Some improvements to the async docs) - #94143 (rustc_const_eval: adopt let else in more places) - #94156 (Gracefully handle non-UTF-8 string slices when pretty printing) - #94186 (Update pin_static_ref stabilization version.) - #94189 (Implement LowerHex on Scalar to clean up their display in rustdoc) - #94190 (Use Metadata::modified instead of FileTime::from_last_modification_ti…) - #94203 (CTFE engine: Scalar: expose size-generic to_(u)int methods) - #94211 (Better error if the user tries to do assignment ... else) - #94215 (trait system: comments and small nonfunctional changes) - #94220 (Correctly handle miniz_oxide extern crate declaration) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
b8967b0d52
36 changed files with 260 additions and 244 deletions
|
@ -231,9 +231,8 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
|
||||||
// Catch such calls and evaluate them instead of trying to load a constant's MIR.
|
// Catch such calls and evaluate them instead of trying to load a constant's MIR.
|
||||||
if let ty::InstanceDef::Intrinsic(def_id) = key.value.instance.def {
|
if let ty::InstanceDef::Intrinsic(def_id) = key.value.instance.def {
|
||||||
let ty = key.value.instance.ty(tcx, key.param_env);
|
let ty = key.value.instance.ty(tcx, key.param_env);
|
||||||
let substs = match ty.kind() {
|
let ty::FnDef(_, substs) = ty.kind() else {
|
||||||
ty::FnDef(_, substs) => substs,
|
bug!("intrinsic with type {:?}", ty);
|
||||||
_ => bug!("intrinsic with type {:?}", ty),
|
|
||||||
};
|
};
|
||||||
return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
|
return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
|
||||||
let span = tcx.def_span(def_id);
|
let span = tcx.def_span(def_id);
|
||||||
|
|
|
@ -318,15 +318,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
||||||
let intrinsic_name = ecx.tcx.item_name(instance.def_id());
|
let intrinsic_name = ecx.tcx.item_name(instance.def_id());
|
||||||
|
|
||||||
// CTFE-specific intrinsics.
|
// CTFE-specific intrinsics.
|
||||||
let (dest, ret) = match ret {
|
let Some((dest, ret)) = ret else {
|
||||||
None => {
|
return Err(ConstEvalErrKind::NeedsRfc(format!(
|
||||||
return Err(ConstEvalErrKind::NeedsRfc(format!(
|
"calling intrinsic `{}`",
|
||||||
"calling intrinsic `{}`",
|
intrinsic_name
|
||||||
intrinsic_name
|
))
|
||||||
))
|
.into());
|
||||||
.into());
|
|
||||||
}
|
|
||||||
Some(p) => p,
|
|
||||||
};
|
};
|
||||||
match intrinsic_name {
|
match intrinsic_name {
|
||||||
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
|
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
|
||||||
|
|
|
@ -631,15 +631,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
// the last field). Can't have foreign types here, how would we
|
// the last field). Can't have foreign types here, how would we
|
||||||
// adjust alignment and size for them?
|
// adjust alignment and size for them?
|
||||||
let field = layout.field(self, layout.fields.count() - 1);
|
let field = layout.field(self, layout.fields.count() - 1);
|
||||||
let (unsized_size, unsized_align) =
|
let Some((unsized_size, unsized_align)) = self.size_and_align_of(metadata, &field)? else {
|
||||||
match self.size_and_align_of(metadata, &field)? {
|
// A field with an extern type. We don't know the actual dynamic size
|
||||||
Some(size_and_align) => size_and_align,
|
// or the alignment.
|
||||||
None => {
|
return Ok(None);
|
||||||
// A field with an extern type. We don't know the actual dynamic size
|
};
|
||||||
// or the alignment.
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME (#26403, #27023): We should be adding padding
|
// FIXME (#26403, #27023): We should be adding padding
|
||||||
// to `sized_size` (to accommodate the `unsized_align`
|
// to `sized_size` (to accommodate the `unsized_align`
|
||||||
|
|
|
@ -84,22 +84,19 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
|
||||||
trace!("intern_shallow {:?} with {:?}", alloc_id, mode);
|
trace!("intern_shallow {:?} with {:?}", alloc_id, mode);
|
||||||
// remove allocation
|
// remove allocation
|
||||||
let tcx = ecx.tcx;
|
let tcx = ecx.tcx;
|
||||||
let (kind, mut alloc) = match ecx.memory.alloc_map.remove(&alloc_id) {
|
let Some((kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else {
|
||||||
Some(entry) => entry,
|
// Pointer not found in local memory map. It is either a pointer to the global
|
||||||
None => {
|
// map, or dangling.
|
||||||
// Pointer not found in local memory map. It is either a pointer to the global
|
// If the pointer is dangling (neither in local nor global memory), we leave it
|
||||||
// map, or dangling.
|
// to validation to error -- it has the much better error messages, pointing out where
|
||||||
// If the pointer is dangling (neither in local nor global memory), we leave it
|
// in the value the dangling reference lies.
|
||||||
// to validation to error -- it has the much better error messages, pointing out where
|
// The `delay_span_bug` ensures that we don't forget such a check in validation.
|
||||||
// in the value the dangling reference lies.
|
if tcx.get_global_alloc(alloc_id).is_none() {
|
||||||
// The `delay_span_bug` ensures that we don't forget such a check in validation.
|
tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
|
||||||
if tcx.get_global_alloc(alloc_id).is_none() {
|
|
||||||
tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
|
|
||||||
}
|
|
||||||
// treat dangling pointers like other statics
|
|
||||||
// just to stop trying to recurse into them
|
|
||||||
return Some(IsStaticOrFn);
|
|
||||||
}
|
}
|
||||||
|
// treat dangling pointers like other statics
|
||||||
|
// just to stop trying to recurse into them
|
||||||
|
return Some(IsStaticOrFn);
|
||||||
};
|
};
|
||||||
// This match is just a canary for future changes to `MemoryKind`, which most likely need
|
// This match is just a canary for future changes to `MemoryKind`, which most likely need
|
||||||
// changes in this function.
|
// changes in this function.
|
||||||
|
|
|
@ -291,21 +291,18 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (alloc_kind, mut alloc) = match self.alloc_map.remove(&alloc_id) {
|
let Some((alloc_kind, mut alloc)) = self.alloc_map.remove(&alloc_id) else {
|
||||||
Some(alloc) => alloc,
|
// Deallocating global memory -- always an error
|
||||||
None => {
|
return Err(match self.tcx.get_global_alloc(alloc_id) {
|
||||||
// Deallocating global memory -- always an error
|
Some(GlobalAlloc::Function(..)) => {
|
||||||
return Err(match self.tcx.get_global_alloc(alloc_id) {
|
err_ub_format!("deallocating {}, which is a function", alloc_id)
|
||||||
Some(GlobalAlloc::Function(..)) => {
|
|
||||||
err_ub_format!("deallocating {}, which is a function", alloc_id)
|
|
||||||
}
|
|
||||||
Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
|
|
||||||
err_ub_format!("deallocating {}, which is static memory", alloc_id)
|
|
||||||
}
|
|
||||||
None => err_ub!(PointerUseAfterFree(alloc_id)),
|
|
||||||
}
|
}
|
||||||
.into());
|
Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
|
||||||
|
err_ub_format!("deallocating {}, which is static memory", alloc_id)
|
||||||
|
}
|
||||||
|
None => err_ub!(PointerUseAfterFree(alloc_id)),
|
||||||
}
|
}
|
||||||
|
.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
if alloc.mutability == Mutability::Not {
|
if alloc.mutability == Mutability::Not {
|
||||||
|
@ -957,9 +954,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||||
ptr: Pointer<Option<M::PointerTag>>,
|
ptr: Pointer<Option<M::PointerTag>>,
|
||||||
size: Size,
|
size: Size,
|
||||||
) -> InterpResult<'tcx, &[u8]> {
|
) -> InterpResult<'tcx, &[u8]> {
|
||||||
let alloc_ref = match self.get(ptr, size, Align::ONE)? {
|
let Some(alloc_ref) = self.get(ptr, size, Align::ONE)? else {
|
||||||
Some(a) => a,
|
// zero-sized access
|
||||||
None => return Ok(&[]), // zero-sized access
|
return Ok(&[]);
|
||||||
};
|
};
|
||||||
// Side-step AllocRef and directly access the underlying bytes more efficiently.
|
// Side-step AllocRef and directly access the underlying bytes more efficiently.
|
||||||
// (We are staying inside the bounds here so all is good.)
|
// (We are staying inside the bounds here so all is good.)
|
||||||
|
@ -983,17 +980,14 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||||
assert_eq!(lower, len, "can only write iterators with a precise length");
|
assert_eq!(lower, len, "can only write iterators with a precise length");
|
||||||
|
|
||||||
let size = Size::from_bytes(len);
|
let size = Size::from_bytes(len);
|
||||||
let alloc_ref = match self.get_mut(ptr, size, Align::ONE)? {
|
let Some(alloc_ref) = self.get_mut(ptr, size, Align::ONE)? else {
|
||||||
Some(alloc_ref) => alloc_ref,
|
// zero-sized access
|
||||||
None => {
|
assert_matches!(
|
||||||
// zero-sized access
|
src.next(),
|
||||||
assert_matches!(
|
None,
|
||||||
src.next(),
|
"iterator said it was empty but returned an element"
|
||||||
None,
|
);
|
||||||
"iterator said it was empty but returned an element"
|
return Ok(());
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Side-step AllocRef and directly access the underlying bytes more efficiently.
|
// Side-step AllocRef and directly access the underlying bytes more efficiently.
|
||||||
|
@ -1043,18 +1037,18 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||||
// and once below to get the underlying `&[mut] Allocation`.
|
// and once below to get the underlying `&[mut] Allocation`.
|
||||||
|
|
||||||
// Source alloc preparations and access hooks.
|
// Source alloc preparations and access hooks.
|
||||||
let (src_alloc_id, src_offset, src) = match src_parts {
|
let Some((src_alloc_id, src_offset, src)) = src_parts else {
|
||||||
None => return Ok(()), // Zero-sized *source*, that means dst is also zero-sized and we have nothing to do.
|
// Zero-sized *source*, that means dst is also zero-sized and we have nothing to do.
|
||||||
Some(src_ptr) => src_ptr,
|
return Ok(());
|
||||||
};
|
};
|
||||||
let src_alloc = self.get_raw(src_alloc_id)?;
|
let src_alloc = self.get_raw(src_alloc_id)?;
|
||||||
let src_range = alloc_range(src_offset, size);
|
let src_range = alloc_range(src_offset, size);
|
||||||
M::memory_read(&self.extra, &src_alloc.extra, src.provenance, src_range)?;
|
M::memory_read(&self.extra, &src_alloc.extra, src.provenance, src_range)?;
|
||||||
// We need the `dest` ptr for the next operation, so we get it now.
|
// We need the `dest` ptr for the next operation, so we get it now.
|
||||||
// We already did the source checks and called the hooks so we are good to return early.
|
// We already did the source checks and called the hooks so we are good to return early.
|
||||||
let (dest_alloc_id, dest_offset, dest) = match dest_parts {
|
let Some((dest_alloc_id, dest_offset, dest)) = dest_parts else {
|
||||||
None => return Ok(()), // Zero-sized *destiantion*.
|
// Zero-sized *destination*.
|
||||||
Some(dest_ptr) => dest_ptr,
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// This checks relocation edges on the src, which needs to happen before
|
// This checks relocation edges on the src, which needs to happen before
|
||||||
|
|
|
@ -258,15 +258,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let alloc = match self.get_alloc(mplace)? {
|
let Some(alloc) = self.get_alloc(mplace)? else {
|
||||||
Some(ptr) => ptr,
|
return Ok(Some(ImmTy {
|
||||||
None => {
|
// zero-sized type
|
||||||
return Ok(Some(ImmTy {
|
imm: Scalar::ZST.into(),
|
||||||
// zero-sized type
|
layout: mplace.layout,
|
||||||
imm: Scalar::ZST.into(),
|
}));
|
||||||
layout: mplace.layout,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match mplace.layout.abi {
|
match mplace.layout.abi {
|
||||||
|
|
|
@ -420,9 +420,8 @@ where
|
||||||
) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, MPlaceTy<'tcx, Tag>>> + 'a>
|
) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, MPlaceTy<'tcx, Tag>>> + 'a>
|
||||||
{
|
{
|
||||||
let len = base.len(self)?; // also asserts that we have a type where this makes sense
|
let len = base.len(self)?; // also asserts that we have a type where this makes sense
|
||||||
let stride = match base.layout.fields {
|
let FieldsShape::Array { stride, .. } = base.layout.fields else {
|
||||||
FieldsShape::Array { stride, .. } => stride,
|
span_bug!(self.cur_span(), "mplace_array_fields: expected an array layout");
|
||||||
_ => span_bug!(self.cur_span(), "mplace_array_fields: expected an array layout"),
|
|
||||||
};
|
};
|
||||||
let layout = base.layout.field(self, 0);
|
let layout = base.layout.field(self, 0);
|
||||||
let dl = &self.tcx.data_layout;
|
let dl = &self.tcx.data_layout;
|
||||||
|
@ -747,9 +746,9 @@ where
|
||||||
|
|
||||||
// Invalid places are a thing: the return place of a diverging function
|
// Invalid places are a thing: the return place of a diverging function
|
||||||
let tcx = *self.tcx;
|
let tcx = *self.tcx;
|
||||||
let mut alloc = match self.get_alloc_mut(dest)? {
|
let Some(mut alloc) = self.get_alloc_mut(dest)? else {
|
||||||
Some(a) => a,
|
// zero-sized access
|
||||||
None => return Ok(()), // zero-sized access
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: We should check that there are dest.layout.size many bytes available in
|
// FIXME: We should check that there are dest.layout.size many bytes available in
|
||||||
|
|
|
@ -46,15 +46,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let loc = match self.frame().loc {
|
let Ok(loc) = self.frame().loc else {
|
||||||
Ok(loc) => loc,
|
// We are unwinding and this fn has no cleanup code.
|
||||||
Err(_) => {
|
// Just go on unwinding.
|
||||||
// We are unwinding and this fn has no cleanup code.
|
trace!("unwinding: skipping frame");
|
||||||
// Just go on unwinding.
|
self.pop_stack_frame(/* unwinding */ true)?;
|
||||||
trace!("unwinding: skipping frame");
|
return Ok(true);
|
||||||
self.pop_stack_frame(/* unwinding */ true)?;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let basic_block = &self.body().basic_blocks()[loc.block];
|
let basic_block = &self.body().basic_blocks()[loc.block];
|
||||||
|
|
||||||
|
|
|
@ -321,10 +321,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
| ty::InstanceDef::CloneShim(..)
|
| ty::InstanceDef::CloneShim(..)
|
||||||
| ty::InstanceDef::Item(_) => {
|
| ty::InstanceDef::Item(_) => {
|
||||||
// We need MIR for this fn
|
// We need MIR for this fn
|
||||||
let (body, instance) =
|
let Some((body, instance)) =
|
||||||
match M::find_mir_or_eval_fn(self, instance, caller_abi, args, ret, unwind)? {
|
M::find_mir_or_eval_fn(self, instance, caller_abi, args, ret, unwind)? else {
|
||||||
Some(body) => body,
|
return Ok(());
|
||||||
None => return Ok(()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute callee information using the `instance` returned by
|
// Compute callee information using the `instance` returned by
|
||||||
|
|
|
@ -697,7 +697,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||||
this.ecx.read_discriminant(op),
|
this.ecx.read_discriminant(op),
|
||||||
this.path,
|
this.path,
|
||||||
err_ub!(InvalidTag(val)) =>
|
err_ub!(InvalidTag(val)) =>
|
||||||
{ "{}", val } expected { "a valid enum tag" },
|
{ "{:x}", val } expected { "a valid enum tag" },
|
||||||
err_ub!(InvalidUninitBytes(None)) =>
|
err_ub!(InvalidUninitBytes(None)) =>
|
||||||
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
||||||
err_unsup!(ReadPointerAsBytes) =>
|
err_unsup!(ReadPointerAsBytes) =>
|
||||||
|
@ -851,12 +851,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||||
// to reject those pointers, we just do not have the machinery to
|
// to reject those pointers, we just do not have the machinery to
|
||||||
// talk about parts of a pointer.
|
// talk about parts of a pointer.
|
||||||
// We also accept uninit, for consistency with the slow path.
|
// We also accept uninit, for consistency with the slow path.
|
||||||
let alloc = match self.ecx.memory.get(mplace.ptr, size, mplace.align)? {
|
let Some(alloc) = self.ecx.memory.get(mplace.ptr, size, mplace.align)? else {
|
||||||
Some(a) => a,
|
// Size 0, nothing more to check.
|
||||||
None => {
|
return Ok(());
|
||||||
// Size 0, nothing more to check.
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let allow_uninit_and_ptr = !M::enforce_number_validity(self.ecx);
|
let allow_uninit_and_ptr = !M::enforce_number_validity(self.ecx);
|
||||||
|
|
|
@ -134,11 +134,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
|
||||||
.find(|(_, block)| matches!(block.terminator().kind, TerminatorKind::Return))
|
.find(|(_, block)| matches!(block.terminator().kind, TerminatorKind::Return))
|
||||||
.map(|(bb, _)| bb);
|
.map(|(bb, _)| bb);
|
||||||
|
|
||||||
let return_block = match return_block {
|
let Some(return_block) = return_block else {
|
||||||
None => {
|
return qualifs::in_any_value_of_ty(ccx, ccx.body.return_ty(), tainted_by_errors);
|
||||||
return qualifs::in_any_value_of_ty(ccx, ccx.body.return_ty(), tainted_by_errors);
|
|
||||||
}
|
|
||||||
Some(bb) => bb,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let return_loc = ccx.body.terminator_loc(return_block);
|
let return_loc = ccx.body.terminator_loc(return_block);
|
||||||
|
|
|
@ -747,15 +747,12 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
|
||||||
if loc.statement_index < num_stmts {
|
if loc.statement_index < num_stmts {
|
||||||
let (mut rvalue, source_info) = {
|
let (mut rvalue, source_info) = {
|
||||||
let statement = &mut self.source[loc.block].statements[loc.statement_index];
|
let statement = &mut self.source[loc.block].statements[loc.statement_index];
|
||||||
let rhs = match statement.kind {
|
let StatementKind::Assign(box (_, ref mut rhs)) = statement.kind else {
|
||||||
StatementKind::Assign(box (_, ref mut rhs)) => rhs,
|
span_bug!(
|
||||||
_ => {
|
statement.source_info.span,
|
||||||
span_bug!(
|
"{:?} is not an assignment",
|
||||||
statement.source_info.span,
|
statement
|
||||||
"{:?} is not an assignment",
|
);
|
||||||
statement
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
@ -15,12 +15,9 @@ where
|
||||||
L: HasLocalDecls<'tcx>,
|
L: HasLocalDecls<'tcx>,
|
||||||
{
|
{
|
||||||
debug!("is_disaligned({:?})", place);
|
debug!("is_disaligned({:?})", place);
|
||||||
let pack = match is_within_packed(tcx, local_decls, place) {
|
let Some(pack) = is_within_packed(tcx, local_decls, place) else {
|
||||||
None => {
|
debug!("is_disaligned({:?}) - not within packed", place);
|
||||||
debug!("is_disaligned({:?}) - not within packed", place);
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Some(pack) => pack,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let ty = place.ty(local_decls, tcx).ty;
|
let ty = place.ty(local_decls, tcx).ty;
|
||||||
|
|
|
@ -132,11 +132,11 @@ type ObligationTreeIdGenerator =
|
||||||
std::iter::Map<std::ops::RangeFrom<usize>, fn(usize) -> ObligationTreeId>;
|
std::iter::Map<std::ops::RangeFrom<usize>, fn(usize) -> ObligationTreeId>;
|
||||||
|
|
||||||
pub struct ObligationForest<O: ForestObligation> {
|
pub struct ObligationForest<O: ForestObligation> {
|
||||||
/// The list of obligations. In between calls to `process_obligations`,
|
/// The list of obligations. In between calls to [Self::process_obligations],
|
||||||
/// this list only contains nodes in the `Pending` or `Waiting` state.
|
/// this list only contains nodes in the `Pending` or `Waiting` state.
|
||||||
///
|
///
|
||||||
/// `usize` indices are used here and throughout this module, rather than
|
/// `usize` indices are used here and throughout this module, rather than
|
||||||
/// `rustc_index::newtype_index!` indices, because this code is hot enough
|
/// [`rustc_index::newtype_index!`] indices, because this code is hot enough
|
||||||
/// that the `u32`-to-`usize` conversions that would be required are
|
/// that the `u32`-to-`usize` conversions that would be required are
|
||||||
/// significant, and space considerations are not important.
|
/// significant, and space considerations are not important.
|
||||||
nodes: Vec<Node<O>>,
|
nodes: Vec<Node<O>>,
|
||||||
|
@ -146,10 +146,11 @@ pub struct ObligationForest<O: ForestObligation> {
|
||||||
|
|
||||||
/// A cache of the nodes in `nodes`, indexed by predicate. Unfortunately,
|
/// A cache of the nodes in `nodes`, indexed by predicate. Unfortunately,
|
||||||
/// its contents are not guaranteed to match those of `nodes`. See the
|
/// its contents are not guaranteed to match those of `nodes`. See the
|
||||||
/// comments in `process_obligation` for details.
|
/// comments in [`Self::process_obligation` for details.
|
||||||
active_cache: FxHashMap<O::CacheKey, usize>,
|
active_cache: FxHashMap<O::CacheKey, usize>,
|
||||||
|
|
||||||
/// A vector reused in compress() and find_cycles_from_node(), to avoid allocating new vectors.
|
/// A vector reused in [Self::compress()] and [Self::find_cycles_from_node()],
|
||||||
|
/// to avoid allocating new vectors.
|
||||||
reused_node_vec: Vec<usize>,
|
reused_node_vec: Vec<usize>,
|
||||||
|
|
||||||
obligation_tree_id_generator: ObligationTreeIdGenerator,
|
obligation_tree_id_generator: ObligationTreeIdGenerator,
|
||||||
|
|
|
@ -123,7 +123,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See `infer::region_constraints::RegionConstraintCollector::leak_check`.
|
/// See [RegionConstraintCollector::leak_check][1].
|
||||||
|
///
|
||||||
|
/// [1]: crate::infer::region_constraints::RegionConstraintCollector::leak_check
|
||||||
pub fn leak_check(
|
pub fn leak_check(
|
||||||
&self,
|
&self,
|
||||||
overly_polymorphic: bool,
|
overly_polymorphic: bool,
|
||||||
|
|
|
@ -100,6 +100,7 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> {
|
||||||
struct LeakCheck<'me, 'tcx> {
|
struct LeakCheck<'me, 'tcx> {
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
universe_at_start_of_snapshot: ty::UniverseIndex,
|
universe_at_start_of_snapshot: ty::UniverseIndex,
|
||||||
|
/// Only used when reporting region errors.
|
||||||
overly_polymorphic: bool,
|
overly_polymorphic: bool,
|
||||||
mini_graph: &'me MiniGraph<'tcx>,
|
mini_graph: &'me MiniGraph<'tcx>,
|
||||||
rcc: &'me RegionConstraintCollector<'me, 'tcx>,
|
rcc: &'me RegionConstraintCollector<'me, 'tcx>,
|
||||||
|
|
|
@ -370,7 +370,7 @@ impl fmt::Display for UndefinedBehaviorInfo<'_> {
|
||||||
InvalidChar(c) => {
|
InvalidChar(c) => {
|
||||||
write!(f, "interpreting an invalid 32-bit value as a char: 0x{:08x}", c)
|
write!(f, "interpreting an invalid 32-bit value as a char: 0x{:08x}", c)
|
||||||
}
|
}
|
||||||
InvalidTag(val) => write!(f, "enum value has invalid tag: {}", val),
|
InvalidTag(val) => write!(f, "enum value has invalid tag: {:x}", val),
|
||||||
InvalidFunctionPointer(p) => {
|
InvalidFunctionPointer(p) => {
|
||||||
write!(f, "using {:?} as function pointer but it does not point to a function", p)
|
write!(f, "using {:?} as function pointer but it does not point to a function", p)
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,16 @@ impl<Tag: Provenance> fmt::Display for Scalar<Tag> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr),
|
Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr),
|
||||||
Scalar::Int(int) => write!(f, "{:?}", int),
|
Scalar::Int(int) => write!(f, "{}", int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Tag: Provenance> fmt::LowerHex for Scalar<Tag> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr),
|
||||||
|
Scalar::Int(int) => write!(f, "0x{:x}", int),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,78 +379,82 @@ impl<'tcx, Tag: Provenance> Scalar<Tag> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the scalar to produce an unsigned integer of the given size.
|
||||||
|
/// Fails if the scalar is a pointer.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_unsigned_with_bit_width(self, bits: u64) -> InterpResult<'static, u128> {
|
pub fn to_uint(self, size: Size) -> InterpResult<'static, u128> {
|
||||||
let sz = Size::from_bits(bits);
|
self.to_bits(size)
|
||||||
self.to_bits(sz)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce a `u8`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce a `u8`. Fails if the scalar is a pointer.
|
||||||
pub fn to_u8(self) -> InterpResult<'static, u8> {
|
pub fn to_u8(self) -> InterpResult<'static, u8> {
|
||||||
self.to_unsigned_with_bit_width(8).map(|v| u8::try_from(v).unwrap())
|
self.to_uint(Size::from_bits(8)).map(|v| u8::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce a `u16`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce a `u16`. Fails if the scalar is a pointer.
|
||||||
pub fn to_u16(self) -> InterpResult<'static, u16> {
|
pub fn to_u16(self) -> InterpResult<'static, u16> {
|
||||||
self.to_unsigned_with_bit_width(16).map(|v| u16::try_from(v).unwrap())
|
self.to_uint(Size::from_bits(16)).map(|v| u16::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce a `u32`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce a `u32`. Fails if the scalar is a pointer.
|
||||||
pub fn to_u32(self) -> InterpResult<'static, u32> {
|
pub fn to_u32(self) -> InterpResult<'static, u32> {
|
||||||
self.to_unsigned_with_bit_width(32).map(|v| u32::try_from(v).unwrap())
|
self.to_uint(Size::from_bits(32)).map(|v| u32::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce a `u64`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce a `u64`. Fails if the scalar is a pointer.
|
||||||
pub fn to_u64(self) -> InterpResult<'static, u64> {
|
pub fn to_u64(self) -> InterpResult<'static, u64> {
|
||||||
self.to_unsigned_with_bit_width(64).map(|v| u64::try_from(v).unwrap())
|
self.to_uint(Size::from_bits(64)).map(|v| u64::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce a `u128`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce a `u128`. Fails if the scalar is a pointer.
|
||||||
pub fn to_u128(self) -> InterpResult<'static, u128> {
|
pub fn to_u128(self) -> InterpResult<'static, u128> {
|
||||||
self.to_unsigned_with_bit_width(128)
|
self.to_uint(Size::from_bits(128))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the scalar to produce a machine-pointer-sized unsigned integer.
|
||||||
|
/// Fails if the scalar is a pointer.
|
||||||
pub fn to_machine_usize(self, cx: &impl HasDataLayout) -> InterpResult<'static, u64> {
|
pub fn to_machine_usize(self, cx: &impl HasDataLayout) -> InterpResult<'static, u64> {
|
||||||
let b = self.to_bits(cx.data_layout().pointer_size)?;
|
let b = self.to_uint(cx.data_layout().pointer_size)?;
|
||||||
Ok(u64::try_from(b).unwrap())
|
Ok(u64::try_from(b).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the scalar to produce a signed integer of the given size.
|
||||||
|
/// Fails if the scalar is a pointer.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_signed_with_bit_width(self, bits: u64) -> InterpResult<'static, i128> {
|
pub fn to_int(self, size: Size) -> InterpResult<'static, i128> {
|
||||||
let sz = Size::from_bits(bits);
|
let b = self.to_bits(size)?;
|
||||||
let b = self.to_bits(sz)?;
|
Ok(size.sign_extend(b) as i128)
|
||||||
Ok(sz.sign_extend(b) as i128)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce an `i8`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce an `i8`. Fails if the scalar is a pointer.
|
||||||
pub fn to_i8(self) -> InterpResult<'static, i8> {
|
pub fn to_i8(self) -> InterpResult<'static, i8> {
|
||||||
self.to_signed_with_bit_width(8).map(|v| i8::try_from(v).unwrap())
|
self.to_int(Size::from_bits(8)).map(|v| i8::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce an `i16`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce an `i16`. Fails if the scalar is a pointer.
|
||||||
pub fn to_i16(self) -> InterpResult<'static, i16> {
|
pub fn to_i16(self) -> InterpResult<'static, i16> {
|
||||||
self.to_signed_with_bit_width(16).map(|v| i16::try_from(v).unwrap())
|
self.to_int(Size::from_bits(16)).map(|v| i16::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce an `i32`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce an `i32`. Fails if the scalar is a pointer.
|
||||||
pub fn to_i32(self) -> InterpResult<'static, i32> {
|
pub fn to_i32(self) -> InterpResult<'static, i32> {
|
||||||
self.to_signed_with_bit_width(32).map(|v| i32::try_from(v).unwrap())
|
self.to_int(Size::from_bits(32)).map(|v| i32::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce an `i64`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce an `i64`. Fails if the scalar is a pointer.
|
||||||
pub fn to_i64(self) -> InterpResult<'static, i64> {
|
pub fn to_i64(self) -> InterpResult<'static, i64> {
|
||||||
self.to_signed_with_bit_width(64).map(|v| i64::try_from(v).unwrap())
|
self.to_int(Size::from_bits(64)).map(|v| i64::try_from(v).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the scalar to produce an `i128`. Fails if the scalar is a pointer.
|
/// Converts the scalar to produce an `i128`. Fails if the scalar is a pointer.
|
||||||
pub fn to_i128(self) -> InterpResult<'static, i128> {
|
pub fn to_i128(self) -> InterpResult<'static, i128> {
|
||||||
self.to_signed_with_bit_width(128)
|
self.to_int(Size::from_bits(128))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the scalar to produce a machine-pointer-sized signed integer.
|
||||||
|
/// Fails if the scalar is a pointer.
|
||||||
pub fn to_machine_isize(self, cx: &impl HasDataLayout) -> InterpResult<'static, i64> {
|
pub fn to_machine_isize(self, cx: &impl HasDataLayout) -> InterpResult<'static, i64> {
|
||||||
let sz = cx.data_layout().pointer_size;
|
let b = self.to_int(cx.data_layout().pointer_size)?;
|
||||||
let b = self.to_bits(sz)?;
|
|
||||||
let b = sz.sign_extend(b) as i128;
|
|
||||||
Ok(i64::try_from(b).unwrap())
|
Ok(i64::try_from(b).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,11 +469,6 @@ impl<'tcx, Tag: Provenance> Scalar<Tag> {
|
||||||
// Going through `u64` to check size and truncation.
|
// Going through `u64` to check size and truncation.
|
||||||
Ok(Double::from_bits(self.to_u64()?.into()))
|
Ok(Double::from_bits(self.to_u64()?.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Replace current `impl Display for Scalar` with `impl LowerHex`.
|
|
||||||
pub fn rustdoc_display(&self) -> String {
|
|
||||||
if let Scalar::Int(int) = self { int.to_string() } else { self.to_string() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, TyEncodable, TyDecodable, HashStable, Hash)]
|
#[derive(Clone, Copy, Eq, PartialEq, TyEncodable, TyDecodable, HashStable, Hash)]
|
||||||
|
@ -494,7 +502,7 @@ impl<Tag: Provenance> fmt::Display for ScalarMaybeUninit<Tag> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ScalarMaybeUninit::Uninit => write!(f, "uninitialized bytes"),
|
ScalarMaybeUninit::Uninit => write!(f, "uninitialized bytes"),
|
||||||
ScalarMaybeUninit::Scalar(s) => write!(f, "{}", s),
|
ScalarMaybeUninit::Scalar(s) => write!(f, "{:x}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1435,8 +1435,7 @@ pub trait PrettyPrinter<'tcx>:
|
||||||
// relocations (we have an active `str` reference here). We don't use this
|
// relocations (we have an active `str` reference here). We don't use this
|
||||||
// result to affect interpreter execution.
|
// result to affect interpreter execution.
|
||||||
let slice = data.inspect_with_uninit_and_ptr_outside_interpreter(start..end);
|
let slice = data.inspect_with_uninit_and_ptr_outside_interpreter(start..end);
|
||||||
let s = std::str::from_utf8(slice).expect("non utf8 str from miri");
|
p!(write("{:?}", String::from_utf8_lossy(slice)));
|
||||||
p!(write("{:?}", s));
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
(ConstValue::ByRef { alloc, offset }, ty::Array(t, n)) if *t == u8_type => {
|
(ConstValue::ByRef { alloc, offset }, ty::Array(t, n)) if *t == u8_type => {
|
||||||
|
|
|
@ -103,6 +103,16 @@ impl<'a> Parser<'a> {
|
||||||
} else {
|
} else {
|
||||||
self.parse_expr_res(Restrictions::STMT_EXPR, Some(attrs))
|
self.parse_expr_res(Restrictions::STMT_EXPR, Some(attrs))
|
||||||
}?;
|
}?;
|
||||||
|
if matches!(e.kind, ExprKind::Assign(..)) && self.eat_keyword(kw::Else) {
|
||||||
|
let bl = self.parse_block()?;
|
||||||
|
// Destructuring assignment ... else.
|
||||||
|
// This is not allowed, but point it out in a nice way.
|
||||||
|
let mut err = self.struct_span_err(
|
||||||
|
e.span.to(bl.span),
|
||||||
|
"<assignment> ... else { ... } is not allowed",
|
||||||
|
);
|
||||||
|
err.emit();
|
||||||
|
}
|
||||||
self.mk_stmt(lo.to(e.span), StmtKind::Expr(e))
|
self.mk_stmt(lo.to(e.span), StmtKind::Expr(e))
|
||||||
} else {
|
} else {
|
||||||
self.error_outer_attrs(&attrs.take_for_recovery());
|
self.error_outer_attrs(&attrs.take_for_recovery());
|
||||||
|
|
|
@ -160,20 +160,12 @@ fn overlap<'cx, 'tcx>(
|
||||||
);
|
);
|
||||||
|
|
||||||
selcx.infcx().probe_maybe_skip_leak_check(skip_leak_check.is_yes(), |snapshot| {
|
selcx.infcx().probe_maybe_skip_leak_check(skip_leak_check.is_yes(), |snapshot| {
|
||||||
overlap_within_probe(
|
overlap_within_probe(selcx, impl1_def_id, impl2_def_id, overlap_mode, snapshot)
|
||||||
selcx,
|
|
||||||
skip_leak_check,
|
|
||||||
impl1_def_id,
|
|
||||||
impl2_def_id,
|
|
||||||
overlap_mode,
|
|
||||||
snapshot,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlap_within_probe<'cx, 'tcx>(
|
fn overlap_within_probe<'cx, 'tcx>(
|
||||||
selcx: &mut SelectionContext<'cx, 'tcx>,
|
selcx: &mut SelectionContext<'cx, 'tcx>,
|
||||||
skip_leak_check: SkipLeakCheck,
|
|
||||||
impl1_def_id: DefId,
|
impl1_def_id: DefId,
|
||||||
impl2_def_id: DefId,
|
impl2_def_id: DefId,
|
||||||
overlap_mode: OverlapMode,
|
overlap_mode: OverlapMode,
|
||||||
|
@ -207,11 +199,11 @@ fn overlap_within_probe<'cx, 'tcx>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skip_leak_check.is_yes() {
|
// We disable the leak when when creating the `snapshot` by using
|
||||||
if infcx.leak_check(true, snapshot).is_err() {
|
// `infcx.probe_maybe_disable_leak_check`.
|
||||||
debug!("overlap: leak check failed");
|
if infcx.leak_check(true, snapshot).is_err() {
|
||||||
return None;
|
debug!("overlap: leak check failed");
|
||||||
}
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let intercrate_ambiguity_causes = selcx.take_intercrate_ambiguity_causes();
|
let intercrate_ambiguity_causes = selcx.take_intercrate_ambiguity_causes();
|
||||||
|
|
|
@ -314,7 +314,7 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
|
||||||
return ProcessResult::Unchanged;
|
return ProcessResult::Unchanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.progress_changed_obligations(pending_obligation)
|
self.process_changed_obligations(pending_obligation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_backedge<'c, I>(
|
fn process_backedge<'c, I>(
|
||||||
|
@ -338,7 +338,7 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
|
||||||
// actually uses this, so move this part of the code
|
// actually uses this, so move this part of the code
|
||||||
// out of that loop.
|
// out of that loop.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn progress_changed_obligations(
|
fn process_changed_obligations(
|
||||||
&mut self,
|
&mut self,
|
||||||
pending_obligation: &mut PendingPredicateObligation<'tcx>,
|
pending_obligation: &mut PendingPredicateObligation<'tcx>,
|
||||||
) -> ProcessResult<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>> {
|
) -> ProcessResult<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>> {
|
||||||
|
|
|
@ -389,7 +389,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
for bound in matching_bounds {
|
for bound in matching_bounds {
|
||||||
// FIXME(oli-obk): it is suspicious that we are dropping the constness and
|
// FIXME(oli-obk): it is suspicious that we are dropping the constness and
|
||||||
// polarity here.
|
// polarity here.
|
||||||
let wc = self.evaluate_where_clause(stack, bound.map_bound(|t| t.trait_ref))?;
|
let wc = self.where_clause_may_apply(stack, bound.map_bound(|t| t.trait_ref))?;
|
||||||
if wc.may_apply() {
|
if wc.may_apply() {
|
||||||
candidates.vec.push(ParamCandidate(bound));
|
candidates.vec.push(ParamCandidate(bound));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1481,7 +1481,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
.map_err(|_| ())
|
.map_err(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_where_clause<'o>(
|
fn where_clause_may_apply<'o>(
|
||||||
&mut self,
|
&mut self,
|
||||||
stack: &TraitObligationStack<'o, 'tcx>,
|
stack: &TraitObligationStack<'o, 'tcx>,
|
||||||
where_clause_trait_ref: ty::PolyTraitRef<'tcx>,
|
where_clause_trait_ref: ty::PolyTraitRef<'tcx>,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::ops;
|
||||||
use crate::pin::Pin;
|
use crate::pin::Pin;
|
||||||
use crate::task::{Context, Poll};
|
use crate::task::{Context, Poll};
|
||||||
|
|
||||||
/// A future represents an asynchronous computation.
|
/// A future represents an asynchronous computation obtained by use of [`async`].
|
||||||
///
|
///
|
||||||
/// A future is a value that might not have finished computing yet. This kind of
|
/// A future is a value that might not have finished computing yet. This kind of
|
||||||
/// "asynchronous value" makes it possible for a thread to continue doing useful
|
/// "asynchronous value" makes it possible for a thread to continue doing useful
|
||||||
|
@ -23,6 +23,7 @@ use crate::task::{Context, Poll};
|
||||||
/// When using a future, you generally won't call `poll` directly, but instead
|
/// When using a future, you generally won't call `poll` directly, but instead
|
||||||
/// `.await` the value.
|
/// `.await` the value.
|
||||||
///
|
///
|
||||||
|
/// [`async`]: ../../std/keyword.async.html
|
||||||
/// [`Waker`]: crate::task::Waker
|
/// [`Waker`]: crate::task::Waker
|
||||||
#[doc(notable_trait)]
|
#[doc(notable_trait)]
|
||||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
#![stable(feature = "futures_api", since = "1.36.0")]
|
#![stable(feature = "futures_api", since = "1.36.0")]
|
||||||
|
|
||||||
//! Asynchronous values.
|
//! Asynchronous basic functionality.
|
||||||
|
//!
|
||||||
|
//! Please see the fundamental [`async`] and [`await`] keywords and the [async book]
|
||||||
|
//! for more information on asynchronous programming in Rust.
|
||||||
|
//!
|
||||||
|
//! [`async`]: ../../std/keyword.async.html
|
||||||
|
//! [`await`]: ../../std/keyword.await.html
|
||||||
|
//! [async book]: https://rust-lang.github.io/async-book/
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ops::{Generator, GeneratorState},
|
ops::{Generator, GeneratorState},
|
||||||
|
|
|
@ -805,7 +805,7 @@ impl<T: ?Sized> Pin<&'static T> {
|
||||||
///
|
///
|
||||||
/// This is safe, because `T` is borrowed for the `'static` lifetime, which
|
/// This is safe, because `T` is borrowed for the `'static` lifetime, which
|
||||||
/// never ends.
|
/// never ends.
|
||||||
#[stable(feature = "pin_static_ref", since = "1.60.0")]
|
#[stable(feature = "pin_static_ref", since = "1.61.0")]
|
||||||
#[rustc_const_unstable(feature = "const_pin", issue = "76654")]
|
#[rustc_const_unstable(feature = "const_pin", issue = "76654")]
|
||||||
pub const fn static_ref(r: &'static T) -> Pin<&'static T> {
|
pub const fn static_ref(r: &'static T) -> Pin<&'static T> {
|
||||||
// SAFETY: The 'static borrow guarantees the data will not be
|
// SAFETY: The 'static borrow guarantees the data will not be
|
||||||
|
@ -858,7 +858,7 @@ impl<T: ?Sized> Pin<&'static mut T> {
|
||||||
///
|
///
|
||||||
/// This is safe, because `T` is borrowed for the `'static` lifetime, which
|
/// This is safe, because `T` is borrowed for the `'static` lifetime, which
|
||||||
/// never ends.
|
/// never ends.
|
||||||
#[stable(feature = "pin_static_ref", since = "1.60.0")]
|
#[stable(feature = "pin_static_ref", since = "1.61.0")]
|
||||||
#[rustc_const_unstable(feature = "const_pin", issue = "76654")]
|
#[rustc_const_unstable(feature = "const_pin", issue = "76654")]
|
||||||
pub const fn static_mut(r: &'static mut T) -> Pin<&'static mut T> {
|
pub const fn static_mut(r: &'static mut T) -> Pin<&'static mut T> {
|
||||||
// SAFETY: The 'static borrow guarantees the data will not be
|
// SAFETY: The 'static borrow guarantees the data will not be
|
||||||
|
|
|
@ -2203,17 +2203,18 @@ mod where_keyword {}
|
||||||
///
|
///
|
||||||
/// Use `async` in front of `fn`, `closure`, or a `block` to turn the marked code into a `Future`.
|
/// Use `async` in front of `fn`, `closure`, or a `block` to turn the marked code into a `Future`.
|
||||||
/// As such the code will not be run immediately, but will only be evaluated when the returned
|
/// As such the code will not be run immediately, but will only be evaluated when the returned
|
||||||
/// future is `.await`ed.
|
/// future is [`.await`]ed.
|
||||||
///
|
///
|
||||||
/// We have written an [async book] detailing async/await and trade-offs compared to using threads.
|
/// We have written an [async book] detailing `async`/`await` and trade-offs compared to using threads.
|
||||||
///
|
///
|
||||||
/// ## Editions
|
/// ## Editions
|
||||||
///
|
///
|
||||||
/// `async` is a keyword from the 2018 edition onwards.
|
/// `async` is a keyword from the 2018 edition onwards.
|
||||||
///
|
///
|
||||||
/// It is available for use in stable rust from version 1.39 onwards.
|
/// It is available for use in stable Rust from version 1.39 onwards.
|
||||||
///
|
///
|
||||||
/// [`Future`]: future::Future
|
/// [`Future`]: future::Future
|
||||||
|
/// [`.await`]: ../std/keyword.await.html
|
||||||
/// [async book]: https://rust-lang.github.io/async-book/
|
/// [async book]: https://rust-lang.github.io/async-book/
|
||||||
mod async_keyword {}
|
mod async_keyword {}
|
||||||
|
|
||||||
|
@ -2221,19 +2222,20 @@ mod async_keyword {}
|
||||||
//
|
//
|
||||||
/// Suspend execution until the result of a [`Future`] is ready.
|
/// Suspend execution until the result of a [`Future`] is ready.
|
||||||
///
|
///
|
||||||
/// `.await`ing a future will suspend the current function's execution until the `executor`
|
/// `.await`ing a future will suspend the current function's execution until the executor
|
||||||
/// has run the future to completion.
|
/// has run the future to completion.
|
||||||
///
|
///
|
||||||
/// Read the [async book] for details on how async/await and executors work.
|
/// Read the [async book] for details on how [`async`]/`await` and executors work.
|
||||||
///
|
///
|
||||||
/// ## Editions
|
/// ## Editions
|
||||||
///
|
///
|
||||||
/// `await` is a keyword from the 2018 edition onwards.
|
/// `await` is a keyword from the 2018 edition onwards.
|
||||||
///
|
///
|
||||||
/// It is available for use in stable rust from version 1.39 onwards.
|
/// It is available for use in stable Rust from version 1.39 onwards.
|
||||||
///
|
///
|
||||||
/// [`Future`]: future::Future
|
/// [`Future`]: future::Future
|
||||||
/// [async book]: https://rust-lang.github.io/async-book/
|
/// [async book]: https://rust-lang.github.io/async-book/
|
||||||
|
/// [`async`]: ../std/keyword.async.html
|
||||||
mod await_keyword {}
|
mod await_keyword {}
|
||||||
|
|
||||||
#[doc(keyword = "dyn")]
|
#[doc(keyword = "dyn")]
|
||||||
|
|
|
@ -366,6 +366,7 @@ extern crate unwind;
|
||||||
|
|
||||||
#[doc(masked)]
|
#[doc(masked)]
|
||||||
#[allow(unused_extern_crates)]
|
#[allow(unused_extern_crates)]
|
||||||
|
#[cfg(feature = "miniz_oxide")]
|
||||||
extern crate miniz_oxide;
|
extern crate miniz_oxide;
|
||||||
|
|
||||||
// During testing, this crate is not actually the "real" std library, but rather
|
// During testing, this crate is not actually the "real" std library, but rather
|
||||||
|
|
|
@ -17,7 +17,6 @@ use std::process::{exit, Command, Stdio};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use build_helper::{output, t, up_to_date};
|
use build_helper::{output, t, up_to_date};
|
||||||
use filetime::FileTime;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::builder::Cargo;
|
use crate::builder::Cargo;
|
||||||
|
@ -1334,8 +1333,9 @@ pub fn run_cargo(
|
||||||
.map(|s| s.starts_with('-') && s.ends_with(&extension[..]))
|
.map(|s| s.starts_with('-') && s.ends_with(&extension[..]))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
});
|
});
|
||||||
let max = candidates
|
let max = candidates.max_by_key(|&&(_, _, ref metadata)| {
|
||||||
.max_by_key(|&&(_, _, ref metadata)| FileTime::from_last_modification_time(metadata));
|
metadata.modified().expect("mtime should be available on all relevant OSes")
|
||||||
|
});
|
||||||
let path_to_add = match max {
|
let path_to_add = match max {
|
||||||
Some(triple) => triple.0.to_str().unwrap(),
|
Some(triple) => triple.0.to_str().unwrap(),
|
||||||
None => panic!("no output generated for {:?} {:?}", prefix, extension),
|
None => panic!("no output generated for {:?} {:?}", prefix, extension),
|
||||||
|
|
|
@ -302,11 +302,7 @@ fn print_const_with_custom_print_scalar(tcx: TyCtxt<'_>, ct: ty::Const<'_>) -> S
|
||||||
// For all other types, fallback to the original `pretty_print_const`.
|
// For all other types, fallback to the original `pretty_print_const`.
|
||||||
match (ct.val(), ct.ty().kind()) {
|
match (ct.val(), ct.ty().kind()) {
|
||||||
(ty::ConstKind::Value(ConstValue::Scalar(int)), ty::Uint(ui)) => {
|
(ty::ConstKind::Value(ConstValue::Scalar(int)), ty::Uint(ui)) => {
|
||||||
format!(
|
format!("{}{}", format_integer_with_underscore_sep(&int.to_string()), ui.name_str())
|
||||||
"{}{}",
|
|
||||||
format_integer_with_underscore_sep(&int.rustdoc_display()),
|
|
||||||
ui.name_str()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
(ty::ConstKind::Value(ConstValue::Scalar(int)), ty::Int(i)) => {
|
(ty::ConstKind::Value(ConstValue::Scalar(int)), ty::Int(i)) => {
|
||||||
let ty = tcx.lift(ct.ty()).unwrap();
|
let ty = tcx.lift(ct.ty()).unwrap();
|
||||||
|
|
|
@ -2,63 +2,69 @@
|
||||||
+ // MIR for `main` after ConstProp
|
+ // MIR for `main` after ConstProp
|
||||||
|
|
||||||
fn main() -> () {
|
fn main() -> () {
|
||||||
let mut _0: (); // return place in scope 0 at $DIR/invalid_constant.rs:13:11: 13:11
|
let mut _0: (); // return place in scope 0 at $DIR/invalid_constant.rs:15:11: 15:11
|
||||||
let _1: main::InvalidChar; // in scope 0 at $DIR/invalid_constant.rs:19:9: 19:22
|
let _1: main::InvalidChar; // in scope 0 at $DIR/invalid_constant.rs:21:9: 21:22
|
||||||
let mut _3: main::InvalidTag; // in scope 0 at $DIR/invalid_constant.rs:26:25: 26:46
|
let mut _3: main::InvalidTag; // in scope 0 at $DIR/invalid_constant.rs:28:25: 28:46
|
||||||
let mut _5: main::NoVariants; // in scope 0 at $DIR/invalid_constant.rs:33:35: 33:56
|
let mut _5: main::NoVariants; // in scope 0 at $DIR/invalid_constant.rs:35:35: 35:56
|
||||||
scope 1 {
|
scope 1 {
|
||||||
debug _invalid_char => _1; // in scope 1 at $DIR/invalid_constant.rs:19:9: 19:22
|
debug _invalid_char => _1; // in scope 1 at $DIR/invalid_constant.rs:21:9: 21:22
|
||||||
let _2: [main::InvalidTag; 1]; // in scope 1 at $DIR/invalid_constant.rs:26:9: 26:21
|
let _2: [main::InvalidTag; 1]; // in scope 1 at $DIR/invalid_constant.rs:28:9: 28:21
|
||||||
scope 2 {
|
scope 2 {
|
||||||
debug _invalid_tag => _2; // in scope 2 at $DIR/invalid_constant.rs:26:9: 26:21
|
debug _invalid_tag => _2; // in scope 2 at $DIR/invalid_constant.rs:28:9: 28:21
|
||||||
let _4: [main::NoVariants; 1]; // in scope 2 at $DIR/invalid_constant.rs:33:9: 33:31
|
let _4: [main::NoVariants; 1]; // in scope 2 at $DIR/invalid_constant.rs:35:9: 35:31
|
||||||
scope 3 {
|
scope 3 {
|
||||||
debug _enum_without_variants => _4; // in scope 3 at $DIR/invalid_constant.rs:33:9: 33:31
|
debug _enum_without_variants => _4; // in scope 3 at $DIR/invalid_constant.rs:35:9: 35:31
|
||||||
|
let _6: main::Str<"<22><><EFBFBD>">; // in scope 3 at $DIR/invalid_constant.rs:39:9: 39:22
|
||||||
|
scope 4 {
|
||||||
|
debug _non_utf8_str => _6; // in scope 4 at $DIR/invalid_constant.rs:39:9: 39:22
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bb0: {
|
bb0: {
|
||||||
StorageLive(_1); // scope 0 at $DIR/invalid_constant.rs:19:9: 19:22
|
StorageLive(_1); // scope 0 at $DIR/invalid_constant.rs:21:9: 21:22
|
||||||
- _1 = const { InvalidChar { int: 0x110001 } }; // scope 0 at $DIR/invalid_constant.rs:19:25: 19:64
|
- _1 = const { InvalidChar { int: 0x110001 } }; // scope 0 at $DIR/invalid_constant.rs:21:25: 21:64
|
||||||
+ _1 = const InvalidChar { int: 1114113_u32, chr: {transmute(0x00110001): char} }; // scope 0 at $DIR/invalid_constant.rs:19:25: 19:64
|
+ _1 = const InvalidChar { int: 1114113_u32, chr: {transmute(0x00110001): char} }; // scope 0 at $DIR/invalid_constant.rs:21:25: 21:64
|
||||||
// ty::Const
|
// ty::Const
|
||||||
// + ty: main::InvalidChar
|
// + ty: main::InvalidChar
|
||||||
- // + val: Unevaluated(main::{constant#0}, [main::InvalidChar], None)
|
- // + val: Unevaluated(main::{constant#0}, [main::InvalidChar], None)
|
||||||
+ // + val: Value(Scalar(0x00110001))
|
+ // + val: Value(Scalar(0x00110001))
|
||||||
// mir::Constant
|
// mir::Constant
|
||||||
// + span: $DIR/invalid_constant.rs:19:25: 19:64
|
// + span: $DIR/invalid_constant.rs:21:25: 21:64
|
||||||
- // + literal: Const { ty: main::InvalidChar, val: Unevaluated(Unevaluated { def: WithOptConstParam { did: DefId(0:7 ~ invalid_constant[726d]::main::{constant#0}), const_param_did: None }, substs: [main::InvalidChar], promoted: None }) }
|
- // + literal: Const { ty: main::InvalidChar, val: Unevaluated(Unevaluated { def: WithOptConstParam { did: DefId(0:7 ~ invalid_constant[726d]::main::{constant#0}), const_param_did: None }, substs: [main::InvalidChar], promoted: None }) }
|
||||||
+ // + literal: Const { ty: main::InvalidChar, val: Value(Scalar(0x00110001)) }
|
+ // + literal: Const { ty: main::InvalidChar, val: Value(Scalar(0x00110001)) }
|
||||||
StorageLive(_2); // scope 1 at $DIR/invalid_constant.rs:26:9: 26:21
|
StorageLive(_2); // scope 1 at $DIR/invalid_constant.rs:28:9: 28:21
|
||||||
StorageLive(_3); // scope 1 at $DIR/invalid_constant.rs:26:25: 26:46
|
StorageLive(_3); // scope 1 at $DIR/invalid_constant.rs:28:25: 28:46
|
||||||
(_3.0: u32) = const 4_u32; // scope 1 at $DIR/invalid_constant.rs:26:25: 26:46
|
(_3.0: u32) = const 4_u32; // scope 1 at $DIR/invalid_constant.rs:28:25: 28:46
|
||||||
- _2 = [move _3]; // scope 1 at $DIR/invalid_constant.rs:26:24: 26:47
|
- _2 = [move _3]; // scope 1 at $DIR/invalid_constant.rs:28:24: 28:47
|
||||||
+ _2 = [const InvalidTag { int: 4_u32, e: Scalar(0x00000004): E }]; // scope 1 at $DIR/invalid_constant.rs:26:24: 26:47
|
+ _2 = [const InvalidTag { int: 4_u32, e: Scalar(0x00000004): E }]; // scope 1 at $DIR/invalid_constant.rs:28:24: 28:47
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: main::InvalidTag
|
+ // + ty: main::InvalidTag
|
||||||
+ // + val: Value(Scalar(0x00000004))
|
+ // + val: Value(Scalar(0x00000004))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/invalid_constant.rs:26:24: 26:47
|
+ // + span: $DIR/invalid_constant.rs:28:24: 28:47
|
||||||
+ // + literal: Const { ty: main::InvalidTag, val: Value(Scalar(0x00000004)) }
|
+ // + literal: Const { ty: main::InvalidTag, val: Value(Scalar(0x00000004)) }
|
||||||
StorageDead(_3); // scope 1 at $DIR/invalid_constant.rs:26:46: 26:47
|
StorageDead(_3); // scope 1 at $DIR/invalid_constant.rs:28:46: 28:47
|
||||||
StorageLive(_4); // scope 2 at $DIR/invalid_constant.rs:33:9: 33:31
|
StorageLive(_4); // scope 2 at $DIR/invalid_constant.rs:35:9: 35:31
|
||||||
StorageLive(_5); // scope 2 at $DIR/invalid_constant.rs:33:35: 33:56
|
StorageLive(_5); // scope 2 at $DIR/invalid_constant.rs:35:35: 35:56
|
||||||
(_5.0: u32) = const 0_u32; // scope 2 at $DIR/invalid_constant.rs:33:35: 33:56
|
(_5.0: u32) = const 0_u32; // scope 2 at $DIR/invalid_constant.rs:35:35: 35:56
|
||||||
- _4 = [move _5]; // scope 2 at $DIR/invalid_constant.rs:33:34: 33:57
|
- _4 = [move _5]; // scope 2 at $DIR/invalid_constant.rs:35:34: 35:57
|
||||||
+ _4 = [const NoVariants { int: 0_u32, empty: Scalar(<ZST>): Empty }]; // scope 2 at $DIR/invalid_constant.rs:33:34: 33:57
|
+ _4 = [const NoVariants { int: 0_u32, empty: Scalar(<ZST>): Empty }]; // scope 2 at $DIR/invalid_constant.rs:35:34: 35:57
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: main::NoVariants
|
+ // + ty: main::NoVariants
|
||||||
+ // + val: Value(Scalar(0x00000000))
|
+ // + val: Value(Scalar(0x00000000))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/invalid_constant.rs:33:34: 33:57
|
+ // + span: $DIR/invalid_constant.rs:35:34: 35:57
|
||||||
+ // + literal: Const { ty: main::NoVariants, val: Value(Scalar(0x00000000)) }
|
+ // + literal: Const { ty: main::NoVariants, val: Value(Scalar(0x00000000)) }
|
||||||
StorageDead(_5); // scope 2 at $DIR/invalid_constant.rs:33:56: 33:57
|
StorageDead(_5); // scope 2 at $DIR/invalid_constant.rs:35:56: 35:57
|
||||||
nop; // scope 0 at $DIR/invalid_constant.rs:13:11: 34:2
|
StorageLive(_6); // scope 3 at $DIR/invalid_constant.rs:39:9: 39:22
|
||||||
StorageDead(_4); // scope 2 at $DIR/invalid_constant.rs:34:1: 34:2
|
nop; // scope 0 at $DIR/invalid_constant.rs:15:11: 42:2
|
||||||
StorageDead(_2); // scope 1 at $DIR/invalid_constant.rs:34:1: 34:2
|
StorageDead(_6); // scope 3 at $DIR/invalid_constant.rs:42:1: 42:2
|
||||||
StorageDead(_1); // scope 0 at $DIR/invalid_constant.rs:34:1: 34:2
|
StorageDead(_4); // scope 2 at $DIR/invalid_constant.rs:42:1: 42:2
|
||||||
return; // scope 0 at $DIR/invalid_constant.rs:34:2: 34:2
|
StorageDead(_2); // scope 1 at $DIR/invalid_constant.rs:42:1: 42:2
|
||||||
|
StorageDead(_1); // scope 0 at $DIR/invalid_constant.rs:42:1: 42:2
|
||||||
|
return; // scope 0 at $DIR/invalid_constant.rs:42:2: 42:2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Verify that we can pretty print invalid constants.
|
// Verify that we can pretty print invalid constants.
|
||||||
|
|
||||||
|
#![feature(adt_const_params)]
|
||||||
#![feature(inline_const)]
|
#![feature(inline_const)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
|
@ -31,4 +33,10 @@ fn main() {
|
||||||
empty: Empty,
|
empty: Empty,
|
||||||
}
|
}
|
||||||
let _enum_without_variants = [NoVariants { int: 0 }];
|
let _enum_without_variants = [NoVariants { int: 0 }];
|
||||||
|
|
||||||
|
// A non-UTF-8 string slice. Regression test for #75763 and #78520.
|
||||||
|
struct Str<const S: &'static str>;
|
||||||
|
let _non_utf8_str: Str::<{
|
||||||
|
unsafe { std::mem::transmute::<&[u8], &str>(&[0xC0, 0xC1, 0xF5]) }
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
// ignore-test
|
|
||||||
// FIXME(const_generics): This test causes an ICE after reverting #76030.
|
|
||||||
#![feature(adt_const_params)]
|
|
||||||
#![allow(incomplete_features)]
|
|
||||||
|
|
||||||
|
|
||||||
struct Bug<const S: &'static str>;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let b: Bug::<{
|
|
||||||
unsafe {
|
|
||||||
// FIXME(adt_const_params): Decide on how to deal with invalid values as const params.
|
|
||||||
std::mem::transmute::<&[u8], &str>(&[0xC0, 0xC1, 0xF5])
|
|
||||||
}
|
|
||||||
}>;
|
|
||||||
}
|
|
18
src/test/ui/let-else/let-else-destructuring.rs
Normal file
18
src/test/ui/let-else/let-else-destructuring.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#![feature(let_else)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Foo {
|
||||||
|
Done,
|
||||||
|
Nested(Option<&'static Foo>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk(mut value: &Foo) {
|
||||||
|
loop {
|
||||||
|
println!("{:?}", value);
|
||||||
|
&Foo::Nested(Some(value)) = value else { break }; //~ ERROR invalid left-hand side of assignment
|
||||||
|
//~^ERROR <assignment> ... else { ... } is not allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
walk(&Foo::Done);
|
||||||
|
}
|
17
src/test/ui/let-else/let-else-destructuring.stderr
Normal file
17
src/test/ui/let-else/let-else-destructuring.stderr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
error: <assignment> ... else { ... } is not allowed
|
||||||
|
--> $DIR/let-else-destructuring.rs:11:9
|
||||||
|
|
|
||||||
|
LL | &Foo::Nested(Some(value)) = value else { break };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error[E0070]: invalid left-hand side of assignment
|
||||||
|
--> $DIR/let-else-destructuring.rs:11:35
|
||||||
|
|
|
||||||
|
LL | &Foo::Nested(Some(value)) = value else { break };
|
||||||
|
| ------------------------- ^
|
||||||
|
| |
|
||||||
|
| cannot assign to this expression
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0070`.
|
Loading…
Add table
Add a link
Reference in a new issue