diff options
Diffstat (limited to 'compiler/rustc_passes/src/loops.rs')
| -rw-r--r-- | compiler/rustc_passes/src/loops.rs | 210 | 
1 files changed, 174 insertions, 36 deletions
| diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs index e10a22cdf31..737310e5c04 100644 --- a/compiler/rustc_passes/src/loops.rs +++ b/compiler/rustc_passes/src/loops.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; +use std::fmt; use Context::*; use rustc_hir as hir; @@ -6,13 +8,14 @@ use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Destination, Node}; use rustc_middle::hir::nested_filter; use rustc_middle::query::Providers; +use rustc_middle::span_bug; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::hygiene::DesugaringKind; use rustc_span::{BytePos, Span}; use crate::errors::{ - BreakInsideAsyncBlock, BreakInsideClosure, BreakNonLoop, ContinueLabeledBlock, OutsideLoop, + BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ContinueLabeledBlock, OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock, }; @@ -22,24 +25,57 @@ enum Context { Fn, Loop(hir::LoopSource), Closure(Span), - AsyncClosure(Span), + Coroutine { coroutine_span: Span, kind: hir::CoroutineDesugaring, source: hir::CoroutineSource }, UnlabeledBlock(Span), + UnlabeledIfBlock(Span), LabeledBlock, Constant, } -#[derive(Copy, Clone)] +#[derive(Clone)] +struct BlockInfo { + name: String, + spans: Vec<Span>, + suggs: Vec<Span>, +} + +#[derive(PartialEq)] +enum BreakContextKind { + Break, + Continue, +} + +impl fmt::Display for BreakContextKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BreakContextKind::Break => "break", + BreakContextKind::Continue => "continue", + } + .fmt(f) + } +} + +#[derive(Clone)] struct CheckLoopVisitor<'a, 'tcx> { sess: &'a Session, tcx: TyCtxt<'tcx>, - cx: Context, + // Keep track of a stack of contexts, so that suggestions + // are not made for contexts where it would be incorrect, + // such as adding a label for an `if`. + // e.g. `if 'foo: {}` would be incorrect. + cx_stack: Vec<Context>, + block_breaks: BTreeMap<Span, BlockInfo>, } fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { - tcx.hir().visit_item_likes_in_module( - module_def_id, - &mut CheckLoopVisitor { sess: tcx.sess, tcx, cx: Normal }, - ); + let mut check = CheckLoopVisitor { + sess: tcx.sess, + tcx, + cx_stack: vec![Normal], + block_breaks: Default::default(), + }; + tcx.hir().visit_item_likes_in_module(module_def_id, &mut check); + check.report_outside_loop_error(); } pub(crate) fn provide(providers: &mut Providers) { @@ -57,10 +93,6 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { self.with_context(Constant, |v| intravisit::walk_anon_const(v, c)); } - fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) { - self.with_context(Constant, |v| intravisit::walk_inline_const(v, c)); - } - fn visit_fn( &mut self, fk: hir::intravisit::FnKind<'hir>, @@ -82,18 +114,55 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) { match e.kind { + hir::ExprKind::If(cond, then, else_opt) => { + self.visit_expr(cond); + + let get_block = |ck_loop: &CheckLoopVisitor<'a, 'hir>, + expr: &hir::Expr<'hir>| + -> Option<&hir::Block<'hir>> { + if let hir::ExprKind::Block(b, None) = expr.kind + && matches!( + ck_loop.cx_stack.last(), + Some(&Normal) + | Some(&Constant) + | Some(&UnlabeledBlock(_)) + | Some(&UnlabeledIfBlock(_)) + ) + { + Some(b) + } else { + None + } + }; + + if let Some(b) = get_block(self, then) { + self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| { + v.visit_block(b) + }); + } else { + self.visit_expr(then); + } + + if let Some(else_expr) = else_opt { + if let Some(b) = get_block(self, else_expr) { + self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| { + v.visit_block(b) + }); + } else { + self.visit_expr(else_expr); + } + } + } hir::ExprKind::Loop(ref b, _, source, _) => { self.with_context(Loop(source), |v| v.visit_block(b)); } hir::ExprKind::Closure(&hir::Closure { ref fn_decl, body, fn_decl_span, kind, .. }) => { - // FIXME(coroutines): This doesn't handle coroutines correctly let cx = match kind { - hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( - hir::CoroutineDesugaring::Async, - hir::CoroutineSource::Block, - )) => AsyncClosure(fn_decl_span), + hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => { + Coroutine { coroutine_span: fn_decl_span, kind, source } + } _ => Closure(fn_decl_span), }; self.visit_fn_decl(fn_decl); @@ -102,11 +171,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { hir::ExprKind::Block(ref b, Some(_label)) => { self.with_context(LabeledBlock, |v| v.visit_block(b)); } - hir::ExprKind::Block(ref b, None) if matches!(self.cx, Fn) => { + hir::ExprKind::Block(ref b, None) if matches!(self.cx_stack.last(), Some(&Fn)) => { self.with_context(Normal, |v| v.visit_block(b)); } hir::ExprKind::Block(ref b, None) - if matches!(self.cx, Normal | Constant | UnlabeledBlock(_)) => + if matches!( + self.cx_stack.last(), + Some(&Normal) | Some(&Constant) | Some(&UnlabeledBlock(_)) + ) => { self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b)); } @@ -179,7 +251,12 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { Some(label) => sp_lo.with_hi(label.ident.span.hi()), None => sp_lo.shrink_to_lo(), }; - self.require_break_cx("break", e.span, label_sp); + self.require_break_cx( + BreakContextKind::Break, + e.span, + label_sp, + self.cx_stack.len() - 1, + ); } hir::ExprKind::Continue(destination) => { self.require_label_in_labeled_block(e.span, &destination, "continue"); @@ -201,7 +278,15 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { } Err(_) => {} } - self.require_break_cx("continue", e.span, e.span) + self.require_break_cx( + BreakContextKind::Continue, + e.span, + e.span, + self.cx_stack.len() - 1, + ) + } + hir::ExprKind::ConstBlock(expr) => { + self.with_context(Constant, |v| intravisit::walk_expr(v, expr)); } _ => intravisit::walk_expr(self, e), } @@ -213,28 +298,67 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> { where F: FnOnce(&mut CheckLoopVisitor<'a, 'hir>), { - let old_cx = self.cx; - self.cx = cx; + self.cx_stack.push(cx); f(self); - self.cx = old_cx; + self.cx_stack.pop(); } - fn require_break_cx(&self, name: &str, span: Span, break_span: Span) { - let is_break = name == "break"; - match self.cx { + fn require_break_cx( + &mut self, + br_cx_kind: BreakContextKind, + span: Span, + break_span: Span, + cx_pos: usize, + ) { + match self.cx_stack[cx_pos] { LabeledBlock | Loop(_) => {} Closure(closure_span) => { - self.sess.dcx().emit_err(BreakInsideClosure { span, closure_span, name }); + self.sess.dcx().emit_err(BreakInsideClosure { + span, + closure_span, + name: &br_cx_kind.to_string(), + }); + } + Coroutine { coroutine_span, kind, source } => { + let kind = match kind { + hir::CoroutineDesugaring::Async => "async", + hir::CoroutineDesugaring::Gen => "gen", + hir::CoroutineDesugaring::AsyncGen => "async gen", + }; + let source = match source { + hir::CoroutineSource::Block => "block", + hir::CoroutineSource::Closure => "closure", + hir::CoroutineSource::Fn => "function", + }; + self.sess.dcx().emit_err(BreakInsideCoroutine { + span, + coroutine_span, + name: &br_cx_kind.to_string(), + kind, + source, + }); } - AsyncClosure(closure_span) => { - self.sess.dcx().emit_err(BreakInsideAsyncBlock { span, closure_span, name }); + UnlabeledBlock(block_span) + if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) => + { + let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo { + name: br_cx_kind.to_string(), + spans: vec![], + suggs: vec![], + }); + block.spans.push(span); + block.suggs.push(break_span); } - UnlabeledBlock(block_span) if is_break && block_span.eq_ctxt(break_span) => { - let suggestion = Some(OutsideLoopSuggestion { block_span, break_span }); - self.sess.dcx().emit_err(OutsideLoop { span, name, is_break, suggestion }); + UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => { + self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1); } - Normal | Constant | Fn | UnlabeledBlock(_) => { - self.sess.dcx().emit_err(OutsideLoop { span, name, is_break, suggestion: None }); + Normal | Constant | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) => { + self.sess.dcx().emit_err(OutsideLoop { + spans: vec![span], + name: &br_cx_kind.to_string(), + is_break: br_cx_kind == BreakContextKind::Break, + suggestion: None, + }); } } } @@ -246,7 +370,7 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> { cf_type: &str, ) -> bool { if !span.is_desugaring(DesugaringKind::QuestionMark) - && self.cx == LabeledBlock + && self.cx_stack.last() == Some(&LabeledBlock) && label.label.is_none() { self.sess.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type }); @@ -254,4 +378,18 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> { } false } + + fn report_outside_loop_error(&mut self) { + for (s, block) in &self.block_breaks { + self.sess.dcx().emit_err(OutsideLoop { + spans: block.spans.clone(), + name: &block.name, + is_break: true, + suggestion: Some(OutsideLoopSuggestion { + block_span: *s, + break_spans: block.suggs.clone(), + }), + }); + } + } } | 
