Auto merge of #65009 - Centril:rollup-06g05xj, r=Centril

Rollup of 13 pull requests

Successful merges:

 - #64581 (Fix unreachable_code warnings for try{} block ok-wrapped expressions)
 - #64850 (Remove inlines from DepNode code)
 - #64914 (regression test for 64453 borrow check error.)
 - #64922 (Use PlaceBuilder to avoid a lot of slice -> vec -> slice convertions)
 - #64948 (Improve sidebar styling to make its integration easier)
 - #64961 (Make comment about dummy type a bit more clear)
 - #64967 (Don't mark borrows of zero-sized arrays as indirectly mutable)
 - #64973 (Fix typo while setting `compile-flags` in test)
 - #64980 (Enable support for `IndirectlyMutableLocals` in `rustc_peek` )
 - #64989 (Fix ICE #64964)
 - #64991 ([const-prop] Correctly handle locals that can't be propagated)
 - #64995 (Remove rustdoc warning)
 - #64997 (rustc book: nitpick SLP vectorization)

Failed merges:

r? @ghost
This commit is contained in:
bors 2019-10-02 18:28:11 +00:00
commit 2daa404e9a
31 changed files with 765 additions and 267 deletions

View File

@ -105,7 +105,7 @@ flag will turn that behavior off.
## no-vectorize-slp
By default, `rustc` will attempt to vectorize loops using [superword-level
By default, `rustc` will attempt to vectorize code using [superword-level
parallelism](https://llvm.org/docs/Vectorizers.html#the-slp-vectorizer). This
flag will turn that behavior off.

View File

@ -114,7 +114,6 @@ macro_rules! define_dep_nodes {
impl DepKind {
#[allow(unreachable_code)]
#[inline]
pub fn can_reconstruct_query_key<$tcx>(&self) -> bool {
match *self {
$(
@ -150,7 +149,6 @@ macro_rules! define_dep_nodes {
}
}
#[inline(always)]
pub fn is_eval_always(&self) -> bool {
match *self {
$(
@ -199,7 +197,6 @@ macro_rules! define_dep_nodes {
impl DepNode {
#[allow(unreachable_code, non_snake_case)]
#[inline(always)]
pub fn new<'tcx>(tcx: TyCtxt<'tcx>,
dep: DepConstructor<'tcx>)
-> DepNode
@ -219,14 +216,16 @@ macro_rules! define_dep_nodes {
hash
};
if cfg!(debug_assertions) &&
!dep_node.kind.can_reconstruct_query_key() &&
(tcx.sess.opts.debugging_opts.incremental_info ||
tcx.sess.opts.debugging_opts.query_dep_graph)
#[cfg(debug_assertions)]
{
tcx.dep_graph.register_dep_node_debug_str(dep_node, || {
arg.to_debug_str(tcx)
});
if !dep_node.kind.can_reconstruct_query_key() &&
(tcx.sess.opts.debugging_opts.incremental_info ||
tcx.sess.opts.debugging_opts.query_dep_graph)
{
tcx.dep_graph.register_dep_node_debug_str(dep_node, || {
arg.to_debug_str(tcx)
});
}
}
return dep_node;
@ -242,14 +241,16 @@ macro_rules! define_dep_nodes {
hash
};
if cfg!(debug_assertions) &&
!dep_node.kind.can_reconstruct_query_key() &&
(tcx.sess.opts.debugging_opts.incremental_info ||
tcx.sess.opts.debugging_opts.query_dep_graph)
#[cfg(debug_assertions)]
{
tcx.dep_graph.register_dep_node_debug_str(dep_node, || {
tupled_args.to_debug_str(tcx)
});
if !dep_node.kind.can_reconstruct_query_key() &&
(tcx.sess.opts.debugging_opts.incremental_info ||
tcx.sess.opts.debugging_opts.query_dep_graph)
{
tcx.dep_graph.register_dep_node_debug_str(dep_node, || {
tupled_args.to_debug_str(tcx)
});
}
}
return dep_node;
@ -267,7 +268,6 @@ macro_rules! define_dep_nodes {
/// Construct a DepNode from the given DepKind and DefPathHash. This
/// method will assert that the given DepKind actually requires a
/// single DefId/DefPathHash parameter.
#[inline(always)]
pub fn from_def_path_hash(kind: DepKind,
def_path_hash: DefPathHash)
-> DepNode {
@ -281,7 +281,6 @@ macro_rules! define_dep_nodes {
/// Creates a new, parameterless DepNode. This method will assert
/// that the DepNode corresponding to the given DepKind actually
/// does not require any parameters.
#[inline(always)]
pub fn new_no_params(kind: DepKind) -> DepNode {
debug_assert!(!kind.has_params());
DepNode {
@ -300,7 +299,6 @@ macro_rules! define_dep_nodes {
/// DepNode. Condition (2) might not be fulfilled if a DepNode
/// refers to something from the previous compilation session that
/// has been removed.
#[inline]
pub fn extract_def_id(&self, tcx: TyCtxt<'_>) -> Option<DefId> {
if self.kind.can_reconstruct_query_key() {
let def_path_hash = DefPathHash(self.hash);
@ -386,14 +384,12 @@ impl fmt::Debug for DepNode {
impl DefPathHash {
#[inline(always)]
pub fn to_dep_node(self, kind: DepKind) -> DepNode {
DepNode::from_def_path_hash(kind, self)
}
}
impl DefId {
#[inline(always)]
pub fn to_dep_node(self, tcx: TyCtxt<'_>, kind: DepKind) -> DepNode {
DepNode::from_def_path_hash(kind, tcx.def_path_hash(self))
}

View File

@ -392,19 +392,35 @@ impl LoweringContext<'_> {
)
}
/// Desugar `try { <stmts>; <expr> }` into `{ <stmts>; ::std::ops::Try::from_ok(<expr>) }`,
/// `try { <stmts>; }` into `{ <stmts>; ::std::ops::Try::from_ok(()) }`
/// and save the block id to use it as a break target for desugaring of the `?` operator.
fn lower_expr_try_block(&mut self, body: &Block) -> hir::ExprKind {
self.with_catch_scope(body.id, |this| {
let unstable_span = this.mark_span_with_reason(
let mut block = this.lower_block(body, true).into_inner();
let try_span = this.mark_span_with_reason(
DesugaringKind::TryBlock,
body.span,
this.allow_try_trait.clone(),
);
let mut block = this.lower_block(body, true).into_inner();
let tail = block.expr.take().map_or_else(
|| this.expr_unit(this.sess.source_map().end_point(unstable_span)),
// Final expression of the block (if present) or `()` with span at the end of block
let tail_expr = block.expr.take().map_or_else(
|| this.expr_unit(this.sess.source_map().end_point(try_span)),
|x: P<hir::Expr>| x.into_inner(),
);
block.expr = Some(this.wrap_in_try_constructor(sym::from_ok, tail, unstable_span));
let ok_wrapped_span = this.mark_span_with_reason(
DesugaringKind::TryBlock,
tail_expr.span,
None
);
// `::std::ops::Try::from_ok($tail_expr)`
block.expr = Some(this.wrap_in_try_constructor(
sym::from_ok, try_span, tail_expr, ok_wrapped_span));
hir::ExprKind::Block(P(block), None)
})
}
@ -412,12 +428,13 @@ impl LoweringContext<'_> {
fn wrap_in_try_constructor(
&mut self,
method: Symbol,
e: hir::Expr,
unstable_span: Span,
method_span: Span,
expr: hir::Expr,
overall_span: Span,
) -> P<hir::Expr> {
let path = &[sym::ops, sym::Try, method];
let from_err = P(self.expr_std_path(unstable_span, path, None, ThinVec::new()));
P(self.expr_call(e.span, from_err, hir_vec![e]))
let constructor = P(self.expr_std_path(method_span, path, None, ThinVec::new()));
P(self.expr_call(overall_span, constructor, hir_vec![expr]))
}
fn lower_arm(&mut self, arm: &Arm) -> hir::Arm {
@ -1244,7 +1261,7 @@ impl LoweringContext<'_> {
self.expr_call_std_path(try_span, from_path, hir_vec![err_expr])
};
let from_err_expr =
self.wrap_in_try_constructor(sym::from_error, from_expr, unstable_span);
self.wrap_in_try_constructor(sym::from_error, unstable_span, from_expr, try_span);
let thin_attrs = ThinVec::from(attrs);
let catch_scope = self.catch_scopes.last().map(|x| *x);
let ret_expr = if let Some(catch_node) = catch_scope {

View File

@ -861,7 +861,7 @@ pub struct Block {
pub span: Span,
/// If true, then there may exist `break 'a` values that aim to
/// break out of this block early.
/// Used by `'label: {}` blocks and by `catch` statements.
/// Used by `'label: {}` blocks and by `try {}` blocks.
pub targeted_by_break: bool,
}

View File

@ -317,6 +317,12 @@ pub struct GeneratorInteriorTypeCause<'tcx> {
pub scope_span: Option<Span>,
}
BraceStructTypeFoldableImpl! {
impl<'tcx> TypeFoldable<'tcx> for GeneratorInteriorTypeCause<'tcx> {
ty, span, scope_span
}
}
#[derive(RustcEncodable, RustcDecodable, Debug)]
pub struct TypeckTables<'tcx> {
/// The HirId::owner all ItemLocalIds in this table are relative to.

View File

@ -600,7 +600,8 @@ impl<'tcx> rustc_serialize::UseSpecializedDecodable for Ty<'tcx> {}
pub type CanonicalTy<'tcx> = Canonical<'tcx, Ty<'tcx>>;
extern {
/// A dummy type used to force `List` to by unsized without requiring fat pointers.
/// A dummy type used to force `List` to be unsized while not requiring references to it be wide
/// pointers.
type OpaqueListContents;
}

View File

@ -6,13 +6,79 @@ use crate::build::{BlockAnd, BlockAndExtension, Builder};
use crate::hair::*;
use rustc::mir::interpret::{PanicInfo::BoundsCheck};
use rustc::mir::*;
use rustc::ty::{CanonicalUserTypeAnnotation, Variance};
use rustc::ty::{CanonicalUserTypeAnnotation, Ty, Variance};
use rustc_index::vec::Idx;
/// `PlaceBuilder` is used to create places during MIR construction. It allows you to "build up" a
/// place by pushing more and more projections onto the end, and then convert the final set into a
/// place using the `into_place` method.
///
/// This is used internally when building a place for an expression like `a.b.c`. The fields `b`
/// and `c` can be progressively pushed onto the place builder that is created when converting `a`.
#[derive(Clone)]
struct PlaceBuilder<'tcx> {
base: PlaceBase<'tcx>,
projection: Vec<PlaceElem<'tcx>>,
}
impl PlaceBuilder<'tcx> {
fn into_place(self) -> Place<'tcx> {
Place {
base: self.base,
projection: self.projection.into_boxed_slice(),
}
}
fn field(self, f: Field, ty: Ty<'tcx>) -> Self {
self.project(PlaceElem::Field(f, ty))
}
fn deref(self) -> Self {
self.project(PlaceElem::Deref)
}
fn index(self, index: Local) -> Self {
self.project(PlaceElem::Index(index))
}
fn project(mut self, elem: PlaceElem<'tcx>) -> Self {
self.projection.push(elem);
self
}
}
impl From<Local> for PlaceBuilder<'tcx> {
fn from(local: Local) -> Self {
Self {
base: local.into(),
projection: Vec::new(),
}
}
}
impl From<PlaceBase<'tcx>> for PlaceBuilder<'tcx> {
fn from(base: PlaceBase<'tcx>) -> Self {
Self {
base,
projection: Vec::new(),
}
}
}
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Compile `expr`, yielding a place that we can move from etc.
pub fn as_place<M>(&mut self, block: BasicBlock, expr: M) -> BlockAnd<Place<'tcx>>
pub fn as_place<M>(&mut self, mut block: BasicBlock, expr: M) -> BlockAnd<Place<'tcx>>
where
M: Mirror<'tcx, Output = Expr<'tcx>>,
{
let place_builder = unpack!(block = self.as_place_builder(block, expr));
block.and(place_builder.into_place())
}
/// This is used when constructing a compound `Place`, so that we can avoid creating
/// intermediate `Place` values until we know the full set of projections.
fn as_place_builder<M>(&mut self, block: BasicBlock, expr: M) -> BlockAnd<PlaceBuilder<'tcx>>
where
M: Mirror<'tcx, Output = Expr<'tcx>>,
{
@ -25,7 +91,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
/// place. The place itself may or may not be mutable:
/// * If this expr is a place expr like a.b, then we will return that place.
/// * Otherwise, a temporary is created: in that event, it will be an immutable temporary.
pub fn as_read_only_place<M>(&mut self, block: BasicBlock, expr: M) -> BlockAnd<Place<'tcx>>
pub fn as_read_only_place<M>(&mut self, mut block: BasicBlock, expr: M) -> BlockAnd<Place<'tcx>>
where
M: Mirror<'tcx, Output = Expr<'tcx>>,
{
let place_builder = unpack!(block = self.as_read_only_place_builder(block, expr));
block.and(place_builder.into_place())
}
/// This is used when constructing a compound `Place`, so that we can avoid creating
/// intermediate `Place` values until we know the full set of projections.
/// Mutability note: The caller of this method promises only to read from the resulting
/// place. The place itself may or may not be mutable:
/// * If this expr is a place expr like a.b, then we will return that place.
/// * Otherwise, a temporary is created: in that event, it will be an immutable temporary.
fn as_read_only_place_builder<M>(
&mut self,
block: BasicBlock,
expr: M,
) -> BlockAnd<PlaceBuilder<'tcx>>
where
M: Mirror<'tcx, Output = Expr<'tcx>>,
{
@ -38,7 +122,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
mut block: BasicBlock,
expr: Expr<'tcx>,
mutability: Mutability,
) -> BlockAnd<Place<'tcx>> {
) -> BlockAnd<PlaceBuilder<'tcx>> {
debug!(
"expr_as_place(block={:?}, expr={:?}, mutability={:?})",
block, expr, mutability
@ -54,25 +138,23 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
value,
} => this.in_scope((region_scope, source_info), lint_level, |this| {
if mutability == Mutability::Not {
this.as_read_only_place(block, value)
this.as_read_only_place_builder(block, value)
} else {
this.as_place(block, value)
this.as_place_builder(block, value)
}
}),
ExprKind::Field { lhs, name } => {
let place = unpack!(block = this.as_place(block, lhs));
let place = place.field(name, expr.ty);
block.and(place)
let place_builder = unpack!(block = this.as_place_builder(block, lhs));
block.and(place_builder.field(name, expr.ty))
}
ExprKind::Deref { arg } => {
let place = unpack!(block = this.as_place(block, arg));
let place = place.deref();
block.and(place)
let place_builder = unpack!(block = this.as_place_builder(block, arg));
block.and(place_builder.deref())
}
ExprKind::Index { lhs, index } => {
let (usize_ty, bool_ty) = (this.hir.usize_ty(), this.hir.bool_ty());
let slice = unpack!(block = this.as_place(block, lhs));
let place_builder = unpack!(block = this.as_place_builder(block, lhs));
// Making this a *fresh* temporary also means we do not have to worry about
// the index changing later: Nothing will ever change this temporary.
// The "retagging" transformation (for Stacked Borrows) relies on this.
@ -83,6 +165,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
Mutability::Not,
));
let slice = place_builder.clone().into_place();
// bounds check:
let (len, lt) = (
this.temp(usize_ty.clone(), expr_span),
@ -92,7 +175,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block,
source_info, // len = len(slice)
&len,
Rvalue::Len(slice.clone()),
Rvalue::Len(slice),
);
this.cfg.push_assign(
block,
@ -110,30 +193,29 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
index: Operand::Copy(Place::from(idx)),
};
let success = this.assert(block, Operand::Move(lt), true, msg, expr_span);
success.and(slice.index(idx))
success.and(place_builder.index(idx))
}
ExprKind::SelfRef => block.and(Place::from(Local::new(1))),
ExprKind::SelfRef => block.and(PlaceBuilder::from(Local::new(1))),
ExprKind::VarRef { id } => {
let place = if this.is_bound_var_in_guard(id) {
let place_builder = if this.is_bound_var_in_guard(id) {
let index = this.var_local_id(id, RefWithinGuard);
Place::from(index).deref()
PlaceBuilder::from(index).deref()
} else {
let index = this.var_local_id(id, OutsideGuard);
Place::from(index)
PlaceBuilder::from(index)
};
block.and(place)
block.and(place_builder)
}
ExprKind::StaticRef { id } => block.and(Place {
base: PlaceBase::Static(Box::new(Static {
ExprKind::StaticRef { id } => block.and(PlaceBuilder::from(
PlaceBase::Static(Box::new(Static {
ty: expr.ty,
kind: StaticKind::Static,
def_id: id,
})),
projection: box [],
}),
}))
)),
ExprKind::PlaceTypeAscription { source, user_ty } => {
let place = unpack!(block = this.as_place(block, source));
let place_builder = unpack!(block = this.as_place_builder(block, source));
if let Some(user_ty) = user_ty {
let annotation_index = this.canonical_user_type_annotations.push(
CanonicalUserTypeAnnotation {
@ -142,13 +224,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
inferred_ty: expr.ty,
}
);
let place = place_builder.clone().into_place();
this.cfg.push(
block,
Statement {
source_info,
kind: StatementKind::AscribeUserType(
box(
place.clone(),
place,
UserTypeProjection { base: annotation_index, projs: vec![], }
),
Variance::Invariant,
@ -156,7 +240,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
},
);
}
block.and(place)
block.and(place_builder)
}
ExprKind::ValueTypeAscription { source, user_ty } => {
let source = this.hir.mirror(source);
@ -185,7 +269,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
},
);
}
block.and(Place::from(temp))
block.and(PlaceBuilder::from(temp))
}
ExprKind::Array { .. }
@ -221,7 +305,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
});
let temp =
unpack!(block = this.as_temp(block, expr.temp_lifetime, expr, mutability));
block.and(Place::from(temp))
block.and(PlaceBuilder::from(temp))
}
}
}

View File

@ -97,6 +97,36 @@ struct TransferFunction<'a, 'mir, 'tcx> {
param_env: ty::ParamEnv<'tcx>,
}
impl<'tcx> TransferFunction<'_, '_, 'tcx> {
/// Returns `true` if this borrow would allow mutation of the `borrowed_place`.
fn borrow_allows_mutation(
&self,
kind: mir::BorrowKind,
borrowed_place: &mir::Place<'tcx>,
) -> bool {
let borrowed_ty = borrowed_place.ty(self.body, self.tcx).ty;
// Zero-sized types cannot be mutated, since there is nothing inside to mutate.
//
// FIXME: For now, we only exempt arrays of length zero. We need to carefully
// consider the effects before extending this to all ZSTs.
if let ty::Array(_, len) = borrowed_ty.kind {
if len.try_eval_usize(self.tcx, self.param_env) == Some(0) {
return false;
}
}
match kind {
mir::BorrowKind::Mut { .. } => true,
| mir::BorrowKind::Shared
| mir::BorrowKind::Shallow
| mir::BorrowKind::Unique
=> !borrowed_ty.is_freeze(self.tcx, self.param_env, DUMMY_SP),
}
}
}
impl<'tcx> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx> {
fn visit_rvalue(
&mut self,
@ -104,21 +134,7 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx> {
location: Location,
) {
if let mir::Rvalue::Ref(_, kind, ref borrowed_place) = *rvalue {
let is_mut = match kind {
mir::BorrowKind::Mut { .. } => true,
| mir::BorrowKind::Shared
| mir::BorrowKind::Shallow
| mir::BorrowKind::Unique
=> {
!borrowed_place
.ty(self.body, self.tcx)
.ty
.is_freeze(self.tcx, self.param_env, DUMMY_SP)
}
};
if is_mut {
if self.borrow_allows_mutation(kind, borrowed_place) {
match borrowed_place.base {
mir::PlaceBase::Local(borrowed_local) if !borrowed_place.is_indirect()
=> self.trans.gen(borrowed_local),

View File

@ -137,7 +137,7 @@ pub fn compute_indirectly_mutable_locals<'mir, 'tcx>(
item.tcx,
item.body,
item.def_id,
&[],
&item.tcx.get_attrs(item.def_id),
&dead_unwinds,
old_dataflow::IndirectlyMutableLocals::new(item.tcx, item.body, item.param_env),
|_, local| old_dataflow::DebugFormatted::new(&local),

View File

@ -335,34 +335,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
fn get_const(&self, local: Local) -> Option<Const<'tcx>> {
let l = &self.ecx.frame().locals[local];
// If the local is `Unitialized` or `Dead` then we haven't propagated a value into it.
//
// `InterpCx::access_local()` mostly takes care of this for us however, for ZSTs,
// it will synthesize a value for us. In doing so, that will cause the
// `get_const(l).is_empty()` assert right before we call `set_const()` in `visit_statement`
// to fail.
if let LocalValue::Uninitialized | LocalValue::Dead = l.value {
return None;
}
self.ecx.access_local(self.ecx.frame(), local, None).ok()
}
fn set_const(&mut self, local: Local, c: Const<'tcx>) {
let frame = self.ecx.frame_mut();
if let Some(layout) = frame.locals[local].layout.get() {
debug_assert_eq!(c.layout, layout);
}
frame.locals[local] = LocalState {
value: LocalValue::Live(*c),
layout: Cell::new(Some(c.layout)),
};
}
fn remove_const(&mut self, local: Local) {
self.ecx.frame_mut().locals[local] = LocalState {
value: LocalValue::Uninitialized,
@ -735,10 +710,8 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
place) {
trace!("checking whether {:?} can be stored to {:?}", value, local);
if self.can_const_prop[local] {
trace!("storing {:?} to {:?}", value, local);
assert!(self.get_const(local).is_none() ||
self.get_const(local) == Some(value));
self.set_const(local, value);
trace!("stored {:?} to {:?}", value, local);
assert_eq!(self.get_const(local), Some(value));
if self.should_const_prop() {
self.replace_with_const(
@ -747,6 +720,9 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
statement.source_info,
);
}
} else {
trace!("can't propagate {:?} to {:?}", value, local);
self.remove_const(local);
}
}
}

View File

@ -3,9 +3,9 @@ use syntax::ast;
use syntax::symbol::sym;
use syntax_pos::Span;
use rustc::ty::{self, TyCtxt};
use rustc::ty::{self, TyCtxt, Ty};
use rustc::hir::def_id::DefId;
use rustc::mir::{self, Body, Location};
use rustc::mir::{self, Body, Location, Local};
use rustc_index::bit_set::BitSet;
use crate::transform::{MirPass, MirSource};
@ -13,9 +13,11 @@ use crate::dataflow::{do_dataflow, DebugFormatted};
use crate::dataflow::MoveDataParamEnv;
use crate::dataflow::BitDenotation;
use crate::dataflow::DataflowResults;
use crate::dataflow::DataflowResultsCursor;
use crate::dataflow::{
DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces
};
use crate::dataflow::IndirectlyMutableLocals;
use crate::dataflow::move_paths::{MovePathIndex, LookupResult};
use crate::dataflow::move_paths::{HasMoveData, MoveData};
@ -50,6 +52,10 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
do_dataflow(tcx, body, def_id, &attributes, &dead_unwinds,
DefinitelyInitializedPlaces::new(tcx, body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]));
let flow_indirectly_mut =
do_dataflow(tcx, body, def_id, &attributes, &dead_unwinds,
IndirectlyMutableLocals::new(tcx, body, param_env),
|_, i| DebugFormatted::new(&i));
if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_init).is_some() {
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_inits);
@ -60,6 +66,9 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
if has_rustc_mir_with(&attributes, sym::rustc_peek_definite_init).is_some() {
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_def_inits);
}
if has_rustc_mir_with(&attributes, sym::rustc_peek_indirectly_mutable).is_some() {
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_indirectly_mut);
}
if has_rustc_mir_with(&attributes, sym::stop_after_dataflow).is_some() {
tcx.sess.fatal("stop_after_dataflow ended compilation");
}
@ -88,151 +97,196 @@ pub fn sanity_check_via_rustc_peek<'tcx, O>(
def_id: DefId,
_attributes: &[ast::Attribute],
results: &DataflowResults<'tcx, O>,
) where
O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
{
) where O: RustcPeekAt<'tcx> {
debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
// FIXME: this is not DRY. Figure out way to abstract this and
// `dataflow::build_sets`. (But note it is doing non-standard
// stuff, so such generalization may not be realistic.)
for bb in body.basic_blocks().indices() {
each_block(tcx, body, results, bb);
let mut cursor = DataflowResultsCursor::new(results, body);
let peek_calls = body
.basic_blocks()
.iter_enumerated()
.filter_map(|(bb, block_data)| {
PeekCall::from_terminator(tcx, block_data.terminator())
.map(|call| (bb, block_data, call))
});
for (bb, block_data, call) in peek_calls {
// Look for a sequence like the following to indicate that we should be peeking at `_1`:
// _2 = &_1;
// rustc_peek(_2);
//
// /* or */
//
// _2 = _1;
// rustc_peek(_2);
let (statement_index, peek_rval) = block_data
.statements
.iter()
.enumerate()
.filter_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
.next()
.expect("call to rustc_peek should be preceded by \
assignment to temporary holding its argument");
match (call.kind, peek_rval) {
| (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
| (PeekCallKind::ByVal, mir::Rvalue::Use(mir::Operand::Move(place)))
| (PeekCallKind::ByVal, mir::Rvalue::Use(mir::Operand::Copy(place)))
=> {
let loc = Location { block: bb, statement_index };
cursor.seek(loc);
let state = cursor.get();
results.operator().peek_at(tcx, place, state, call);
}
_ => {
let msg = "rustc_peek: argument expression \
must be either `place` or `&place`";
tcx.sess.span_err(call.span, msg);
}
}
}
}
fn each_block<'tcx, O>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
results: &DataflowResults<'tcx, O>,
bb: mir::BasicBlock,
) where
O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
{
let move_data = results.0.operator.move_data();
let mir::BasicBlockData { ref statements, ref terminator, is_cleanup: _ } = body[bb];
let (args, span) = match is_rustc_peek(tcx, terminator) {
Some(args_and_span) => args_and_span,
None => return,
};
assert!(args.len() == 1);
let peek_arg_place = match args[0] {
mir::Operand::Copy(ref place @ mir::Place {
base: mir::PlaceBase::Local(_),
projection: box [],
}) |
mir::Operand::Move(ref place @ mir::Place {
base: mir::PlaceBase::Local(_),
projection: box [],
}) => Some(place),
_ => None,
};
let peek_arg_place = match peek_arg_place {
Some(arg) => arg,
None => {
tcx.sess.diagnostic().span_err(
span, "dataflow::sanity_check cannot feed a non-temp to rustc_peek.");
return;
}
};
let mut on_entry = results.0.sets.entry_set_for(bb.index()).to_owned();
let mut trans = results.0.sets.trans_for(bb.index()).clone();
// Emulate effect of all statements in the block up to (but not
// including) the borrow within `peek_arg_place`. Do *not* include
// call to `peek_arg_place` itself (since we are peeking the state
// of the argument at time immediate preceding Call to
// `rustc_peek`).
for (j, stmt) in statements.iter().enumerate() {
debug!("rustc_peek: ({:?},{}) {:?}", bb, j, stmt);
let (place, rvalue) = match stmt.kind {
mir::StatementKind::Assign(box(ref place, ref rvalue)) => {
(place, rvalue)
/// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
/// RHS of the assignment.
fn value_assigned_to_local<'a, 'tcx>(
stmt: &'a mir::Statement<'tcx>,
local: Local,
) -> Option<&'a mir::Rvalue<'tcx>> {
if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
if let mir::Place { base: mir::PlaceBase::Local(l), projection: box [] } = place {
if local == *l {
return Some(&*rvalue);
}
}
}
None
}
#[derive(Clone, Copy, Debug)]
enum PeekCallKind {
ByVal,
ByRef,
}
impl PeekCallKind {
fn from_arg_ty(arg: Ty<'_>) -> Self {
match arg.kind {
ty::Ref(_, _, _) => PeekCallKind::ByRef,
_ => PeekCallKind::ByVal,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PeekCall {
arg: Local,
kind: PeekCallKind,
span: Span,
}
impl PeekCall {
fn from_terminator<'tcx>(
tcx: TyCtxt<'tcx>,
terminator: &mir::Terminator<'tcx>,
) -> Option<Self> {
use mir::{Operand, Place, PlaceBase};
let span = terminator.source_info.span;
if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
&terminator.kind
{
if let ty::FnDef(def_id, substs) = func.literal.ty.kind {
let sig = tcx.fn_sig(def_id);
let name = tcx.item_name(def_id);
if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek {
return None;
}
assert_eq!(args.len(), 1);
let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
let arg = match args[0] {
| Operand::Copy(Place { base: PlaceBase::Local(local), projection: box [] })
| Operand::Move(Place { base: PlaceBase::Local(local), projection: box [] })
=> local,
_ => {
tcx.sess.diagnostic().span_err(
span, "dataflow::sanity_check cannot feed a non-temp to rustc_peek.");
return None;
}
};
return Some(PeekCall {
arg,
kind,
span,
});
}
}
None
}
}
pub trait RustcPeekAt<'tcx>: BitDenotation<'tcx> {
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: &mir::Place<'tcx>,
flow_state: &BitSet<Self::Idx>,
call: PeekCall,
);
}
impl<'tcx, O> RustcPeekAt<'tcx> for O
where O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
{
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: &mir::Place<'tcx>,
flow_state: &BitSet<Self::Idx>,
call: PeekCall,
) {
match self.move_data().rev_lookup.find(place.as_ref()) {
LookupResult::Exact(peek_mpi) => {
let bit_state = flow_state.contains(peek_mpi);
debug!("rustc_peek({:?} = &{:?}) bit_state: {}",
call.arg, place, bit_state);
if !bit_state {
tcx.sess.span_err(call.span, "rustc_peek: bit not set");
}
}
LookupResult::Parent(..) => {
tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
}
}
}
}
impl<'tcx> RustcPeekAt<'tcx> for IndirectlyMutableLocals<'_, 'tcx> {
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: &mir::Place<'tcx>,
flow_state: &BitSet<Local>,
call: PeekCall,
) {
warn!("peek_at: place={:?}", place);
let local = match place {
mir::Place { base: mir::PlaceBase::Local(l), projection: box [] } => *l,
_ => {
tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
return;
}
mir::StatementKind::FakeRead(..) |
mir::StatementKind::StorageLive(_) |
mir::StatementKind::StorageDead(_) |
mir::StatementKind::InlineAsm { .. } |
mir::StatementKind::Retag { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => continue,
mir::StatementKind::SetDiscriminant{ .. } =>
span_bug!(stmt.source_info.span,
"sanity_check should run before Deaggregator inserts SetDiscriminant"),
};
if place == peek_arg_place {
if let mir::Rvalue::Ref(_, mir::BorrowKind::Shared, ref peeking_at_place) = *rvalue {
// Okay, our search is over.
match move_data.rev_lookup.find(peeking_at_place.as_ref()) {
LookupResult::Exact(peek_mpi) => {
let bit_state = on_entry.contains(peek_mpi);
debug!("rustc_peek({:?} = &{:?}) bit_state: {}",
place, peeking_at_place, bit_state);
if !bit_state {
tcx.sess.span_err(span, "rustc_peek: bit not set");
}
}
LookupResult::Parent(..) => {
tcx.sess.span_err(span, "rustc_peek: argument untracked");
}
}
return;
} else {
// Our search should have been over, but the input
// does not match expectations of `rustc_peek` for
// this sanity_check.
let msg = "rustc_peek: argument expression \
must be immediate borrow of form `&expr`";
tcx.sess.span_err(span, msg);
}
}
let lhs_mpi = move_data.rev_lookup.find(place.as_ref());
debug!("rustc_peek: computing effect on place: {:?} ({:?}) in stmt: {:?}",
place, lhs_mpi, stmt);
// reset GEN and KILL sets before emulating their effect.
trans.clear();
results.0.operator.before_statement_effect(
&mut trans,
Location { block: bb, statement_index: j });
results.0.operator.statement_effect(
&mut trans,
Location { block: bb, statement_index: j });
trans.apply(&mut on_entry);
}
results.0.operator.before_terminator_effect(
&mut trans,
Location { block: bb, statement_index: statements.len() });
tcx.sess.span_err(span, &format!("rustc_peek: MIR did not match \
anticipated pattern; note that \
rustc_peek expects input of \
form `&expr`"));
}
fn is_rustc_peek<'a, 'tcx>(
tcx: TyCtxt<'tcx>,
terminator: &'a Option<mir::Terminator<'tcx>>,
) -> Option<(&'a [mir::Operand<'tcx>], Span)> {
if let Some(mir::Terminator { ref kind, source_info, .. }) = *terminator {
if let mir::TerminatorKind::Call { func: ref oper, ref args, .. } = *kind {
if let mir::Operand::Constant(ref func) = *oper {
if let ty::FnDef(def_id, _) = func.literal.ty.kind {
let abi = tcx.fn_sig(def_id).abi();
let name = tcx.item_name(def_id);
if abi == Abi::RustIntrinsic && name == sym::rustc_peek {
return Some((args, source_info.span));
}
}
}
if !flow_state.contains(local) {
tcx.sess.span_err(call.span, "rustc_peek: bit not set");
}
}
return None;
}

View File

@ -18,6 +18,7 @@ use crate::util::nodemap::FxHashMap;
use crate::astconv::AstConv as _;
use errors::{Applicability, DiagnosticBuilder, pluralise};
use syntax_pos::hygiene::DesugaringKind;
use syntax::ast;
use syntax::symbol::{Symbol, kw, sym};
use syntax::source_map::Span;
@ -150,8 +151,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
debug!(">> type-checking: expr={:?} expected={:?}",
expr, expected);
// True if `expr` is a `Try::from_ok(())` that is a result of desugaring a try block
// without the final expr (e.g. `try { return; }`). We don't want to generate an
// unreachable_code lint for it since warnings for autogenerated code are confusing.
let is_try_block_generated_unit_expr = match expr.kind {
ExprKind::Call(_, ref args) if expr.span.is_desugaring(DesugaringKind::TryBlock) =>
args.len() == 1 && args[0].span.is_desugaring(DesugaringKind::TryBlock),
_ => false,
};
// Warn for expressions after diverging siblings.
self.warn_if_unreachable(expr.hir_id, expr.span, "expression");
if !is_try_block_generated_unit_expr {
self.warn_if_unreachable(expr.hir_id, expr.span, "expression");
}
// Hide the outer diverging and has_errors flags.
let old_diverges = self.diverges.get();
@ -164,6 +177,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Warn for non-block expressions with diverging children.
match expr.kind {
ExprKind::Block(..) | ExprKind::Loop(..) | ExprKind::Match(..) => {},
// If `expr` is a result of desugaring the try block and is an ok-wrapped
// diverging expression (e.g. it arose from desugaring of `try { return }`),
// we skip issuing a warning because it is autogenerated code.
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {},
ExprKind::Call(ref callee, _) =>
self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
ExprKind::MethodCall(_, ref span, _) =>

View File

@ -123,13 +123,6 @@ pub fn resolve_interior<'a, 'tcx>(
// Sort types by insertion order
types.sort_by_key(|t| t.1);
// Store the generator types and spans into the tables for this generator.
let interior_types = types.iter().cloned().map(|t| t.0).collect::<Vec<_>>();
visitor.fcx.inh.tables.borrow_mut().generator_interior_types = interior_types;
// Extract type components
let type_list = fcx.tcx.mk_type_list(types.into_iter().map(|t| (t.0).ty));
// The types in the generator interior contain lifetimes local to the generator itself,
// which should not be exposed outside of the generator. Therefore, we replace these
// lifetimes with existentially-bound lifetimes, which reflect the exact value of the
@ -139,18 +132,25 @@ pub fn resolve_interior<'a, 'tcx>(
// if a Sync generator contains an &'α T, we need to check whether &'α T: Sync),
// so knowledge of the exact relationships between them isn't particularly important.
debug!("types in generator {:?}, span = {:?}", type_list, body.value.span);
debug!("types in generator {:?}, span = {:?}", types, body.value.span);
// Replace all regions inside the generator interior with late bound regions
// Note that each region slot in the types gets a new fresh late bound region,
// which means that none of the regions inside relate to any other, even if
// typeck had previously found constraints that would cause them to be related.
let mut counter = 0;
let type_list = fcx.tcx.fold_regions(&type_list, &mut false, |_, current_depth| {
let types = fcx.tcx.fold_regions(&types, &mut false, |_, current_depth| {
counter += 1;
fcx.tcx.mk_region(ty::ReLateBound(current_depth, ty::BrAnon(counter)))
});
// Store the generator types and spans into the tables for this generator.
let interior_types = types.iter().map(|t| t.0.clone()).collect::<Vec<_>>();
visitor.fcx.inh.tables.borrow_mut().generator_interior_types = interior_types;
// Extract type components
let type_list = fcx.tcx.mk_type_list(types.into_iter().map(|t| (t.0).ty));
let witness = fcx.tcx.mk_generator_witness(ty::Binder::bind(type_list));
debug!("types in generator after region replacement {:?}, span = {:?}",

View File

@ -473,7 +473,7 @@ pub enum Diverges {
WarnedAlways
}
// Convenience impls for combinig `Diverges`.
// Convenience impls for combining `Diverges`.
impl ops::BitAnd for Diverges {
type Output = Self;

View File

@ -183,7 +183,7 @@ nav.sub {
position: fixed;
left: 0;
top: 0;
height: 100vh;
bottom: 0;
overflow: auto;
}
@ -573,7 +573,7 @@ h4 > code, h3 > code, .invisible > code {
margin-top: 0;
}
nav {
nav:not(.sidebar) {
border-bottom: 1px solid;
padding-bottom: 10px;
margin-bottom: 10px;

View File

@ -129,7 +129,7 @@ pre {
pre.rust .comment { color: #8d8d8b; }
pre.rust .doccomment { color: #8ca375; }
nav {
nav:not(.sidebar) {
border-bottom-color: #4e4e4e;
}
nav.main .current {

View File

@ -129,7 +129,7 @@ pre {
pre.rust .comment { color: #8E908C; }
pre.rust .doccomment { color: #4D4D4C; }
nav {
nav:not(.sidebar) {
border-bottom-color: #e0e0e0;
}
nav.main .current {

View File

@ -597,6 +597,7 @@ symbols! {
rustc_peek_definite_init,
rustc_peek_maybe_init,
rustc_peek_maybe_uninit,
rustc_peek_indirectly_mutable,
rustc_private,
rustc_proc_macro_decls,
rustc_promotable,

View File

@ -1109,7 +1109,7 @@ pub enum RunStrategy {
InProcess,
/// Spawns a subprocess to run the test, and sends the result back over the
/// supplied channel. Requires argv[0] to exist and point to the binary
/// supplied channel. Requires `argv[0]` to exist and point to the binary
/// that's currently running.
SpawnPrimary,
}

View File

@ -0,0 +1,22 @@
// check-pass
// compile-flags: -Z query-dep-graph
// edition:2018
// Regression test for ICE related to `await`ing in a method + incr. comp. (#64964)
struct Body;
impl Body {
async fn next(&mut self) {
async {}.await
}
}
// Another reproduction: `await`ing with a variable from for-loop.
async fn bar() {
for x in 0..10 {
async { Some(x) }.await.unwrap();
}
}
fn main() {}

View File

@ -0,0 +1,24 @@
struct Project;
struct Value;
static settings_dir: String = format!("");
//~^ ERROR [E0019]
//~| ERROR [E0015]
//~| ERROR [E0015]
fn from_string(_: String) -> Value {
Value
}
fn set_editor(_: Value) {}
fn main() {
let settings_data = from_string(settings_dir);
//~^ ERROR cannot move out of static item `settings_dir` [E0507]
let args: i32 = 0;
match args {
ref x if x == &0 => set_editor(settings_data),
ref x if x == &1 => set_editor(settings_data),
_ => unimplemented!(),
}
}

View File

@ -0,0 +1,34 @@
error[E0507]: cannot move out of static item `settings_dir`
--> $DIR/issue-64453.rs:15:37
|
LL | let settings_data = from_string(settings_dir);
| ^^^^^^^^^^^^ move occurs because `settings_dir` has type `std::string::String`, which does not implement the `Copy` trait
error[E0019]: static contains unimplemented expression type
--> $DIR/issue-64453.rs:4:31
|
LL | static settings_dir: String = format!("");
| ^^^^^^^^^^^
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
--> $DIR/issue-64453.rs:4:31
|
LL | static settings_dir: String = format!("");
| ^^^^^^^^^^^
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
--> $DIR/issue-64453.rs:4:31
|
LL | static settings_dir: String = format!("");
| ^^^^^^^^^^^
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
error: aborting due to 4 previous errors
Some errors have detailed explanations: E0015, E0019, E0507.
For more information about an error, try `rustc --explain E0015`.

View File

@ -0,0 +1,31 @@
// Several variants of #64945.
// This struct is not important, we just use it to put `T` and `'a` in scope for our associated
// consts.
struct Generic<'a, T>(std::marker::PhantomData<&'a T>);
impl<'a, T: 'static> Generic<'a, T> {
const EMPTY_SLICE: &'a [T] = {
let x: &'a [T] = &[];
x
};
const EMPTY_SLICE_REF: &'a &'static [T] = {
let x: &'static [T] = &[];
&x
//~^ ERROR `x` does not live long enough
};
}
static mut INTERIOR_MUT_AND_DROP: &'static [std::cell::RefCell<Vec<i32>>] = {
let x: &[_] = &[];
x
};
static mut INTERIOR_MUT_AND_DROP_REF: &'static &'static [std::cell::RefCell<Vec<i32>>] = {
let x: &[_] = &[];
&x
//~^ ERROR `x` does not live long enough
};
fn main() {}

View File

@ -0,0 +1,30 @@
error[E0597]: `x` does not live long enough
--> $DIR/generic-slice.rs:15:9
|
LL | impl<'a, T: 'static> Generic<'a, T> {
| -- lifetime `'a` defined here
...
LL | &x
| ^^
| |
| borrowed value does not live long enough
| using this value as a constant requires that `x` is borrowed for `'a`
LL |
LL | };
| - `x` dropped here while still borrowed
error[E0597]: `x` does not live long enough
--> $DIR/generic-slice.rs:27:5
|
LL | &x
| ^^
| |
| borrowed value does not live long enough
| using this value as a static requires that `x` is borrowed for `'static`
LL |
LL | };
| - `x` dropped here while still borrowed
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0597`.

View File

@ -0,0 +1,15 @@
// run-pass
fn main() {
foo(10);
}
fn foo(mut n: i32) {
if false {
n = 0i32;
}
if n > 0i32 {
1i32 / n;
}
}

View File

@ -0,0 +1,8 @@
warning: unused arithmetic operation that must be used
--> $DIR/issue-64970.rs:13:9
|
LL | 1i32 / n;
| ^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default

View File

@ -1,5 +1,5 @@
// build-pass (FIXME(62277): could be check-pass?)
// compiler-flags: -g
// build-pass
// compile-flags: -g
pub struct Dst {
pub a: (),

View File

@ -0,0 +1,42 @@
// compile-flags: -Zunleash-the-miri-inside-of-you
#![feature(core_intrinsics, rustc_attrs, const_raw_ptr_deref)]
use std::cell::UnsafeCell;
use std::intrinsics::rustc_peek;
#[repr(C)]
struct PartialInteriorMut {
zst: [i32; 0],
cell: UnsafeCell<i32>,
}
#[rustc_mir(rustc_peek_indirectly_mutable,stop_after_dataflow)]
#[rustc_mir(borrowck_graphviz_postflow="indirect.dot")]
const BOO: i32 = {
let x = PartialInteriorMut {
zst: [],
cell: UnsafeCell::new(0),
};
let p_zst: *const _ = &x.zst ; // Doesn't cause `x` to get marked as indirectly mutable.
let rmut_cell = unsafe {
// Take advantage of the fact that `zst` and `cell` are at the same location in memory.
// This trick would work with any size type if miri implemented `ptr::offset`.
let p_cell = p_zst as *const UnsafeCell<i32>;
let pmut_cell = (*p_cell).get();
&mut *pmut_cell
};
*rmut_cell = 42; // Mutates `x` indirectly even though `x` is not marked indirectly mutable!!!
let val = *rmut_cell;
unsafe { rustc_peek(x) }; //~ ERROR rustc_peek: bit not set
val
};
fn main() {
println!("{}", BOO);
}

View File

@ -0,0 +1,10 @@
error: rustc_peek: bit not set
--> $DIR/indirect-mutation-offset.rs:35:14
|
LL | unsafe { rustc_peek(x) };
| ^^^^^^^^^^^^^
error: stop_after_dataflow ended compilation
error: aborting due to 2 previous errors

View File

@ -0,0 +1,76 @@
// Test unreachable_code lint for `try {}` block ok-wrapping. See issues #54165, #63324.
// compile-flags: --edition 2018
// check-pass
#![feature(try_blocks)]
#![warn(unreachable_code)]
fn err() -> Result<u32, ()> {
Err(())
}
// In the following cases unreachable code is autogenerated and should not be reported.
fn test_ok_wrapped_divergent_expr_1() {
let res: Result<u32, ()> = try {
loop {
err()?;
}
};
println!("res: {:?}", res);
}
fn test_ok_wrapped_divergent_expr_2() {
let _: Result<u32, ()> = try {
return
};
}
fn test_autogenerated_unit_after_divergent_expr() {
let _: Result<(), ()> = try {
return;
};
}
// In the following cases unreachable code should be reported.
fn test_try_block_after_divergent_stmt() {
let _: Result<u32, ()> = {
return;
try {
loop {
err()?;
}
}
// ~^^^^^ WARNING unreachable expression
};
}
fn test_wrapped_divergent_expr() {
let _: Result<u32, ()> = {
Err(return)
// ~^ WARNING unreachable call
};
}
fn test_expr_after_divergent_stmt_in_try_block() {
let res: Result<u32, ()> = try {
loop {
err()?;
}
42
// ~^ WARNING unreachable expression
};
println!("res: {:?}", res);
}
fn main() {
test_ok_wrapped_divergent_expr_1();
test_ok_wrapped_divergent_expr_2();
test_autogenerated_unit_after_divergent_expr();
test_try_block_after_divergent_stmt();
test_wrapped_divergent_expr();
test_expr_after_divergent_stmt_in_try_block();
}

View File

@ -0,0 +1,38 @@
warning: unreachable expression
--> $DIR/try-block-unreachable-code-lint.rs:41:9
|
LL | return;
| ------ any code following this expression is unreachable
LL |
LL | / try {
LL | | loop {
LL | | err()?;
LL | | }
LL | | }
| |_________^ unreachable expression
|
note: lint level defined here
--> $DIR/try-block-unreachable-code-lint.rs:6:9
|
LL | #![warn(unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: unreachable call
--> $DIR/try-block-unreachable-code-lint.rs:52:9
|
LL | Err(return)
| ^^^ ------ any code following this expression is unreachable
| |
| unreachable call
warning: unreachable expression
--> $DIR/try-block-unreachable-code-lint.rs:63:9
|
LL | / loop {
LL | | err()?;
LL | | }
| |_________- any code following this expression is unreachable
LL |
LL | 42
| ^^ unreachable expression