rustc_hir_typeck/
loops.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use Context::*;
5use rustc_hir as hir;
6use rustc_hir::def::DefKind;
7use rustc_hir::def_id::LocalDefId;
8use rustc_hir::intravisit::{self, Visitor};
9use rustc_hir::{Destination, Node};
10use rustc_middle::hir::nested_filter;
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
76pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir::Body<'tcx>) {
77    let mut check =
78        CheckLoopVisitor { tcx, cx_stack: vec![Normal], block_breaks: Default::default() };
79    let cx = match tcx.def_kind(def_id) {
80        DefKind::AnonConst => AnonConst,
81        _ => Fn,
82    };
83    check.with_context(cx, |v| v.visit_body(body));
84    check.report_outside_loop_error();
85}
86
87impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
88    type NestedFilter = nested_filter::OnlyBodies;
89
90    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
91        self.tcx
92    }
93
94    fn visit_anon_const(&mut self, _: &'hir hir::AnonConst) {
95        // Typecked on its own.
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_expr(&mut self, e: &'hir hir::Expr<'hir>) {
103        match e.kind {
104            hir::ExprKind::If(cond, then, else_opt) => {
105                self.visit_expr(cond);
106
107                let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
108                                 expr: &hir::Expr<'hir>|
109                 -> Option<&hir::Block<'hir>> {
110                    if let hir::ExprKind::Block(b, None) = expr.kind
111                        && matches!(
112                            ck_loop.cx_stack.last(),
113                            Some(&Normal)
114                                | Some(&AnonConst)
115                                | Some(&UnlabeledBlock(_))
116                                | Some(&UnlabeledIfBlock(_))
117                        )
118                    {
119                        Some(b)
120                    } else {
121                        None
122                    }
123                };
124
125                if let Some(b) = get_block(self, then) {
126                    self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
127                        v.visit_block(b)
128                    });
129                } else {
130                    self.visit_expr(then);
131                }
132
133                if let Some(else_expr) = else_opt {
134                    if let Some(b) = get_block(self, else_expr) {
135                        self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
136                            v.visit_block(b)
137                        });
138                    } else {
139                        self.visit_expr(else_expr);
140                    }
141                }
142            }
143            hir::ExprKind::Loop(ref b, _, source, _) => {
144                self.with_context(Loop(source), |v| v.visit_block(b));
145            }
146            hir::ExprKind::Closure(&hir::Closure {
147                ref fn_decl, body, fn_decl_span, kind, ..
148            }) => {
149                let cx = match kind {
150                    hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
151                        Coroutine { coroutine_span: fn_decl_span, kind, source }
152                    }
153                    _ => Closure(fn_decl_span),
154                };
155                self.visit_fn_decl(fn_decl);
156                self.with_context(cx, |v| v.visit_nested_body(body));
157            }
158            hir::ExprKind::Block(ref b, Some(_label)) => {
159                self.with_context(LabeledBlock, |v| v.visit_block(b));
160            }
161            hir::ExprKind::Block(ref b, None)
162                if matches!(self.cx_stack.last(), Some(&Fn) | Some(&ConstBlock)) =>
163            {
164                self.with_context(Normal, |v| v.visit_block(b));
165            }
166            hir::ExprKind::Block(
167                ref b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
168                None,
169            ) if matches!(
170                self.cx_stack.last(),
171                Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
172            ) =>
173            {
174                self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
175            }
176            hir::ExprKind::Break(break_label, ref opt_expr) => {
177                if let Some(e) = opt_expr {
178                    self.visit_expr(e);
179                }
180
181                if self.require_label_in_labeled_block(e.span, &break_label, "break") {
182                    // If we emitted an error about an unlabeled break in a labeled
183                    // block, we don't need any further checking for this break any more
184                    return;
185                }
186
187                let loop_id = match break_label.target_id {
188                    Ok(loop_id) => Some(loop_id),
189                    Err(hir::LoopIdError::OutsideLoopScope) => None,
190                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
191                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
192                            span: e.span,
193                            cf_type: "break",
194                        });
195                        None
196                    }
197                    Err(hir::LoopIdError::UnresolvedLabel) => None,
198                };
199
200                if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
201                    return;
202                }
203
204                if let Some(break_expr) = opt_expr {
205                    let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
206                        match self.tcx.hir_expect_expr(loop_id).kind {
207                            hir::ExprKind::Loop(_, label, source, sp) => {
208                                (Some(sp), label, Some(source))
209                            }
210                            ref r => {
211                                span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
212                            }
213                        }
214                    } else {
215                        (None, None, None)
216                    };
217                    match loop_kind {
218                        None | Some(hir::LoopSource::Loop) => (),
219                        Some(kind) => {
220                            let suggestion = format!(
221                                "break{}",
222                                break_label
223                                    .label
224                                    .map_or_else(String::new, |l| format!(" {}", l.ident))
225                            );
226                            self.tcx.dcx().emit_err(BreakNonLoop {
227                                span: e.span,
228                                head,
229                                kind: kind.name(),
230                                suggestion,
231                                loop_label,
232                                break_label: break_label.label,
233                                break_expr_kind: &break_expr.kind,
234                                break_expr_span: break_expr.span,
235                            });
236                        }
237                    }
238                }
239
240                let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
241                let label_sp = match break_label.label {
242                    Some(label) => sp_lo.with_hi(label.ident.span.hi()),
243                    None => sp_lo.shrink_to_lo(),
244                };
245                self.require_break_cx(
246                    BreakContextKind::Break,
247                    e.span,
248                    label_sp,
249                    self.cx_stack.len() - 1,
250                );
251            }
252            hir::ExprKind::Continue(destination) => {
253                self.require_label_in_labeled_block(e.span, &destination, "continue");
254
255                match destination.target_id {
256                    Ok(loop_id) => {
257                        if let Node::Block(block) = self.tcx.hir_node(loop_id) {
258                            self.tcx.dcx().emit_err(ContinueLabeledBlock {
259                                span: e.span,
260                                block_span: block.span,
261                            });
262                        }
263                    }
264                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
265                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
266                            span: e.span,
267                            cf_type: "continue",
268                        });
269                    }
270                    Err(_) => {}
271                }
272                self.require_break_cx(
273                    BreakContextKind::Continue,
274                    e.span,
275                    e.span,
276                    self.cx_stack.len() - 1,
277                )
278            }
279            _ => intravisit::walk_expr(self, e),
280        }
281    }
282}
283
284impl<'hir> CheckLoopVisitor<'hir> {
285    fn with_context<F>(&mut self, cx: Context, f: F)
286    where
287        F: FnOnce(&mut CheckLoopVisitor<'hir>),
288    {
289        self.cx_stack.push(cx);
290        f(self);
291        self.cx_stack.pop();
292    }
293
294    fn require_break_cx(
295        &mut self,
296        br_cx_kind: BreakContextKind,
297        span: Span,
298        break_span: Span,
299        cx_pos: usize,
300    ) {
301        match self.cx_stack[cx_pos] {
302            LabeledBlock | Loop(_) => {}
303            Closure(closure_span) => {
304                self.tcx.dcx().emit_err(BreakInsideClosure {
305                    span,
306                    closure_span,
307                    name: &br_cx_kind.to_string(),
308                });
309            }
310            Coroutine { coroutine_span, kind, source } => {
311                let kind = match kind {
312                    hir::CoroutineDesugaring::Async => "async",
313                    hir::CoroutineDesugaring::Gen => "gen",
314                    hir::CoroutineDesugaring::AsyncGen => "async gen",
315                };
316                let source = match source {
317                    hir::CoroutineSource::Block => "block",
318                    hir::CoroutineSource::Closure => "closure",
319                    hir::CoroutineSource::Fn => "function",
320                };
321                self.tcx.dcx().emit_err(BreakInsideCoroutine {
322                    span,
323                    coroutine_span,
324                    name: &br_cx_kind.to_string(),
325                    kind,
326                    source,
327                });
328            }
329            UnlabeledBlock(block_span)
330                if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
331            {
332                let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
333                    name: br_cx_kind.to_string(),
334                    spans: vec![],
335                    suggs: vec![],
336                });
337                block.spans.push(span);
338                block.suggs.push(break_span);
339            }
340            UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
341                self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
342            }
343            Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
344                self.tcx.dcx().emit_err(OutsideLoop {
345                    spans: vec![span],
346                    name: &br_cx_kind.to_string(),
347                    is_break: br_cx_kind == BreakContextKind::Break,
348                    suggestion: None,
349                });
350            }
351        }
352    }
353
354    fn require_label_in_labeled_block(
355        &self,
356        span: Span,
357        label: &Destination,
358        cf_type: &str,
359    ) -> bool {
360        if !span.is_desugaring(DesugaringKind::QuestionMark)
361            && self.cx_stack.last() == Some(&LabeledBlock)
362            && label.label.is_none()
363        {
364            self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
365            return true;
366        }
367        false
368    }
369
370    fn report_outside_loop_error(&self) {
371        for (s, block) in &self.block_breaks {
372            self.tcx.dcx().emit_err(OutsideLoop {
373                spans: block.spans.clone(),
374                name: &block.name,
375                is_break: true,
376                suggestion: Some(OutsideLoopSuggestion {
377                    block_span: *s,
378                    break_spans: block.suggs.clone(),
379                }),
380            });
381        }
382    }
383}