rustc_passes/
loops.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use Context::*;
5use rustc_hir as hir;
6use rustc_hir::def_id::{LocalDefId, LocalModDefId};
7use rustc_hir::intravisit::{self, Visitor};
8use rustc_hir::{Destination, Node};
9use rustc_middle::hir::nested_filter;
10use rustc_middle::query::Providers;
11use rustc_middle::span_bug;
12use rustc_middle::ty::TyCtxt;
13use rustc_span::hygiene::DesugaringKind;
14use rustc_span::{BytePos, Span};
15
16use crate::errors::{
17    BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ContinueLabeledBlock, OutsideLoop,
18    OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock,
19};
20
21/// The context in which a block is encountered.
22#[derive(Clone, Copy, Debug, PartialEq)]
23enum Context {
24    Normal,
25    Fn,
26    Loop(hir::LoopSource),
27    Closure(Span),
28    Coroutine {
29        coroutine_span: Span,
30        kind: hir::CoroutineDesugaring,
31        source: hir::CoroutineSource,
32    },
33    UnlabeledBlock(Span),
34    UnlabeledIfBlock(Span),
35    LabeledBlock,
36    /// E.g. The labeled block inside `['_'; 'block: { break 'block 1 + 2; }]`.
37    AnonConst,
38    /// E.g. `const { ... }`.
39    ConstBlock,
40}
41
42#[derive(Clone)]
43struct BlockInfo {
44    name: String,
45    spans: Vec<Span>,
46    suggs: Vec<Span>,
47}
48
49#[derive(PartialEq)]
50enum BreakContextKind {
51    Break,
52    Continue,
53}
54
55impl fmt::Display for BreakContextKind {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            BreakContextKind::Break => "break",
59            BreakContextKind::Continue => "continue",
60        }
61        .fmt(f)
62    }
63}
64
65#[derive(Clone)]
66struct CheckLoopVisitor<'tcx> {
67    tcx: TyCtxt<'tcx>,
68    // Keep track of a stack of contexts, so that suggestions
69    // are not made for contexts where it would be incorrect,
70    // such as adding a label for an `if`.
71    // e.g. `if 'foo: {}` would be incorrect.
72    cx_stack: Vec<Context>,
73    block_breaks: BTreeMap<Span, BlockInfo>,
74}
75
76fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
77    let mut check =
78        CheckLoopVisitor { tcx, cx_stack: vec![Normal], block_breaks: Default::default() };
79    tcx.hir().visit_item_likes_in_module(module_def_id, &mut check);
80    check.report_outside_loop_error();
81}
82
83pub(crate) fn provide(providers: &mut Providers) {
84    *providers = Providers { check_mod_loops, ..*providers };
85}
86
87impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
88    type NestedFilter = nested_filter::OnlyBodies;
89
90    fn nested_visit_map(&mut self) -> Self::Map {
91        self.tcx.hir()
92    }
93
94    fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
95        self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
96    }
97
98    fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) {
99        self.with_context(ConstBlock, |v| intravisit::walk_inline_const(v, c));
100    }
101
102    fn visit_fn(
103        &mut self,
104        fk: hir::intravisit::FnKind<'hir>,
105        fd: &'hir hir::FnDecl<'hir>,
106        b: hir::BodyId,
107        _: Span,
108        id: LocalDefId,
109    ) {
110        self.with_context(Fn, |v| intravisit::walk_fn(v, fk, fd, b, id));
111    }
112
113    fn visit_trait_item(&mut self, trait_item: &'hir hir::TraitItem<'hir>) {
114        self.with_context(Fn, |v| intravisit::walk_trait_item(v, trait_item));
115    }
116
117    fn visit_impl_item(&mut self, impl_item: &'hir hir::ImplItem<'hir>) {
118        self.with_context(Fn, |v| intravisit::walk_impl_item(v, impl_item));
119    }
120
121    fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
122        match e.kind {
123            hir::ExprKind::If(cond, then, else_opt) => {
124                self.visit_expr(cond);
125
126                let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
127                                 expr: &hir::Expr<'hir>|
128                 -> Option<&hir::Block<'hir>> {
129                    if let hir::ExprKind::Block(b, None) = expr.kind
130                        && matches!(
131                            ck_loop.cx_stack.last(),
132                            Some(&Normal)
133                                | Some(&AnonConst)
134                                | Some(&UnlabeledBlock(_))
135                                | Some(&UnlabeledIfBlock(_))
136                        )
137                    {
138                        Some(b)
139                    } else {
140                        None
141                    }
142                };
143
144                if let Some(b) = get_block(self, then) {
145                    self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
146                        v.visit_block(b)
147                    });
148                } else {
149                    self.visit_expr(then);
150                }
151
152                if let Some(else_expr) = else_opt {
153                    if let Some(b) = get_block(self, else_expr) {
154                        self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
155                            v.visit_block(b)
156                        });
157                    } else {
158                        self.visit_expr(else_expr);
159                    }
160                }
161            }
162            hir::ExprKind::Loop(ref b, _, source, _) => {
163                self.with_context(Loop(source), |v| v.visit_block(b));
164            }
165            hir::ExprKind::Closure(&hir::Closure {
166                ref fn_decl, body, fn_decl_span, kind, ..
167            }) => {
168                let cx = match kind {
169                    hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
170                        Coroutine { coroutine_span: fn_decl_span, kind, source }
171                    }
172                    _ => Closure(fn_decl_span),
173                };
174                self.visit_fn_decl(fn_decl);
175                self.with_context(cx, |v| v.visit_nested_body(body));
176            }
177            hir::ExprKind::Block(ref b, Some(_label)) => {
178                self.with_context(LabeledBlock, |v| v.visit_block(b));
179            }
180            hir::ExprKind::Block(ref b, None)
181                if matches!(self.cx_stack.last(), Some(&Fn) | Some(&ConstBlock)) =>
182            {
183                self.with_context(Normal, |v| v.visit_block(b));
184            }
185            hir::ExprKind::Block(
186                ref b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
187                None,
188            ) if matches!(
189                self.cx_stack.last(),
190                Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
191            ) =>
192            {
193                self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
194            }
195            hir::ExprKind::Break(break_label, ref opt_expr) => {
196                if let Some(e) = opt_expr {
197                    self.visit_expr(e);
198                }
199
200                if self.require_label_in_labeled_block(e.span, &break_label, "break") {
201                    // If we emitted an error about an unlabeled break in a labeled
202                    // block, we don't need any further checking for this break any more
203                    return;
204                }
205
206                let loop_id = match break_label.target_id {
207                    Ok(loop_id) => Some(loop_id),
208                    Err(hir::LoopIdError::OutsideLoopScope) => None,
209                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
210                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
211                            span: e.span,
212                            cf_type: "break",
213                        });
214                        None
215                    }
216                    Err(hir::LoopIdError::UnresolvedLabel) => None,
217                };
218
219                if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
220                    return;
221                }
222
223                if let Some(break_expr) = opt_expr {
224                    let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
225                        match self.tcx.hir().expect_expr(loop_id).kind {
226                            hir::ExprKind::Loop(_, label, source, sp) => {
227                                (Some(sp), label, Some(source))
228                            }
229                            ref r => {
230                                span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
231                            }
232                        }
233                    } else {
234                        (None, None, None)
235                    };
236                    match loop_kind {
237                        None | Some(hir::LoopSource::Loop) => (),
238                        Some(kind) => {
239                            let suggestion = format!(
240                                "break{}",
241                                break_label
242                                    .label
243                                    .map_or_else(String::new, |l| format!(" {}", l.ident))
244                            );
245                            self.tcx.dcx().emit_err(BreakNonLoop {
246                                span: e.span,
247                                head,
248                                kind: kind.name(),
249                                suggestion,
250                                loop_label,
251                                break_label: break_label.label,
252                                break_expr_kind: &break_expr.kind,
253                                break_expr_span: break_expr.span,
254                            });
255                        }
256                    }
257                }
258
259                let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
260                let label_sp = match break_label.label {
261                    Some(label) => sp_lo.with_hi(label.ident.span.hi()),
262                    None => sp_lo.shrink_to_lo(),
263                };
264                self.require_break_cx(
265                    BreakContextKind::Break,
266                    e.span,
267                    label_sp,
268                    self.cx_stack.len() - 1,
269                );
270            }
271            hir::ExprKind::Continue(destination) => {
272                self.require_label_in_labeled_block(e.span, &destination, "continue");
273
274                match destination.target_id {
275                    Ok(loop_id) => {
276                        if let Node::Block(block) = self.tcx.hir_node(loop_id) {
277                            self.tcx.dcx().emit_err(ContinueLabeledBlock {
278                                span: e.span,
279                                block_span: block.span,
280                            });
281                        }
282                    }
283                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
284                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
285                            span: e.span,
286                            cf_type: "continue",
287                        });
288                    }
289                    Err(_) => {}
290                }
291                self.require_break_cx(
292                    BreakContextKind::Continue,
293                    e.span,
294                    e.span,
295                    self.cx_stack.len() - 1,
296                )
297            }
298            _ => intravisit::walk_expr(self, e),
299        }
300    }
301}
302
303impl<'hir> CheckLoopVisitor<'hir> {
304    fn with_context<F>(&mut self, cx: Context, f: F)
305    where
306        F: FnOnce(&mut CheckLoopVisitor<'hir>),
307    {
308        self.cx_stack.push(cx);
309        f(self);
310        self.cx_stack.pop();
311    }
312
313    fn require_break_cx(
314        &mut self,
315        br_cx_kind: BreakContextKind,
316        span: Span,
317        break_span: Span,
318        cx_pos: usize,
319    ) {
320        match self.cx_stack[cx_pos] {
321            LabeledBlock | Loop(_) => {}
322            Closure(closure_span) => {
323                self.tcx.dcx().emit_err(BreakInsideClosure {
324                    span,
325                    closure_span,
326                    name: &br_cx_kind.to_string(),
327                });
328            }
329            Coroutine { coroutine_span, kind, source } => {
330                let kind = match kind {
331                    hir::CoroutineDesugaring::Async => "async",
332                    hir::CoroutineDesugaring::Gen => "gen",
333                    hir::CoroutineDesugaring::AsyncGen => "async gen",
334                };
335                let source = match source {
336                    hir::CoroutineSource::Block => "block",
337                    hir::CoroutineSource::Closure => "closure",
338                    hir::CoroutineSource::Fn => "function",
339                };
340                self.tcx.dcx().emit_err(BreakInsideCoroutine {
341                    span,
342                    coroutine_span,
343                    name: &br_cx_kind.to_string(),
344                    kind,
345                    source,
346                });
347            }
348            UnlabeledBlock(block_span)
349                if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
350            {
351                let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
352                    name: br_cx_kind.to_string(),
353                    spans: vec![],
354                    suggs: vec![],
355                });
356                block.spans.push(span);
357                block.suggs.push(break_span);
358            }
359            UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
360                self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
361            }
362            Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
363                self.tcx.dcx().emit_err(OutsideLoop {
364                    spans: vec![span],
365                    name: &br_cx_kind.to_string(),
366                    is_break: br_cx_kind == BreakContextKind::Break,
367                    suggestion: None,
368                });
369            }
370        }
371    }
372
373    fn require_label_in_labeled_block(
374        &self,
375        span: Span,
376        label: &Destination,
377        cf_type: &str,
378    ) -> bool {
379        if !span.is_desugaring(DesugaringKind::QuestionMark)
380            && self.cx_stack.last() == Some(&LabeledBlock)
381            && label.label.is_none()
382        {
383            self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
384            return true;
385        }
386        false
387    }
388
389    fn report_outside_loop_error(&self) {
390        for (s, block) in &self.block_breaks {
391            self.tcx.dcx().emit_err(OutsideLoop {
392                spans: block.spans.clone(),
393                name: &block.name,
394                is_break: true,
395                suggestion: Some(OutsideLoopSuggestion {
396                    block_span: *s,
397                    break_spans: block.suggs.clone(),
398                }),
399            });
400        }
401    }
402}