Skip to main content

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, find_attr};
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, ConstContinueBadLabel,
18    ContinueLabeledBlock, OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition,
19    UnlabeledInLabeledBlock,
20};
21
22/// The context in which a block is encountered.
23#[derive(#[automatically_derived]
impl ::core::clone::Clone for Context {
    #[inline]
    fn clone(&self) -> Context {
        let _: ::core::clone::AssertParamIsClone<hir::LoopSource>;
        let _: ::core::clone::AssertParamIsClone<Span>;
        let _: ::core::clone::AssertParamIsClone<hir::CoroutineDesugaring>;
        let _: ::core::clone::AssertParamIsClone<hir::CoroutineSource>;
        let _: ::core::clone::AssertParamIsClone<Destination>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Context { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for Context {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Context::Normal => ::core::fmt::Formatter::write_str(f, "Normal"),
            Context::Fn => ::core::fmt::Formatter::write_str(f, "Fn"),
            Context::Loop(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Loop",
                    &__self_0),
            Context::Closure(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Closure", &__self_0),
            Context::Coroutine {
                coroutine_span: __self_0, kind: __self_1, source: __self_2 }
                =>
                ::core::fmt::Formatter::debug_struct_field3_finish(f,
                    "Coroutine", "coroutine_span", __self_0, "kind", __self_1,
                    "source", &__self_2),
            Context::UnlabeledBlock(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "UnlabeledBlock", &__self_0),
            Context::UnlabeledIfBlock(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "UnlabeledIfBlock", &__self_0),
            Context::LabeledBlock =>
                ::core::fmt::Formatter::write_str(f, "LabeledBlock"),
            Context::AnonConst =>
                ::core::fmt::Formatter::write_str(f, "AnonConst"),
            Context::ConstBlock =>
                ::core::fmt::Formatter::write_str(f, "ConstBlock"),
            Context::LoopMatch { labeled_block: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "LoopMatch", "labeled_block", &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Context {
    #[inline]
    fn eq(&self, other: &Context) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (Context::Loop(__self_0), Context::Loop(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Context::Closure(__self_0), Context::Closure(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Context::Coroutine {
                    coroutine_span: __self_0, kind: __self_1, source: __self_2
                    }, Context::Coroutine {
                    coroutine_span: __arg1_0, kind: __arg1_1, source: __arg1_2
                    }) =>
                    __self_0 == __arg1_0 && __self_1 == __arg1_1 &&
                        __self_2 == __arg1_2,
                (Context::UnlabeledBlock(__self_0),
                    Context::UnlabeledBlock(__arg1_0)) => __self_0 == __arg1_0,
                (Context::UnlabeledIfBlock(__self_0),
                    Context::UnlabeledIfBlock(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Context::LoopMatch { labeled_block: __self_0 },
                    Context::LoopMatch { labeled_block: __arg1_0 }) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq)]
24enum Context {
25    Normal,
26    Fn,
27    Loop(hir::LoopSource),
28    Closure(Span),
29    Coroutine {
30        coroutine_span: Span,
31        kind: hir::CoroutineDesugaring,
32        source: hir::CoroutineSource,
33    },
34    UnlabeledBlock(Span),
35    UnlabeledIfBlock(Span),
36    LabeledBlock,
37    /// E.g. The labeled block inside `['_'; 'block: { break 'block 1 + 2; }]`.
38    AnonConst,
39    /// E.g. `const { ... }`.
40    ConstBlock,
41    /// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`.
42    LoopMatch {
43        /// The destination pointing to the labeled block (not to the loop itself).
44        labeled_block: Destination,
45    },
46}
47
48#[derive(#[automatically_derived]
impl ::core::clone::Clone for BlockInfo {
    #[inline]
    fn clone(&self) -> BlockInfo {
        BlockInfo {
            name: ::core::clone::Clone::clone(&self.name),
            spans: ::core::clone::Clone::clone(&self.spans),
            suggs: ::core::clone::Clone::clone(&self.suggs),
        }
    }
}Clone)]
49struct BlockInfo {
50    name: String,
51    spans: Vec<Span>,
52    suggs: Vec<Span>,
53}
54
55#[derive(#[automatically_derived]
impl ::core::cmp::PartialEq for BreakContextKind {
    #[inline]
    fn eq(&self, other: &BreakContextKind) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
56enum BreakContextKind {
57    Break,
58    Continue,
59}
60
61impl fmt::Display for BreakContextKind {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            BreakContextKind::Break => "break",
65            BreakContextKind::Continue => "continue",
66        }
67        .fmt(f)
68    }
69}
70
71#[derive(#[automatically_derived]
impl<'tcx> ::core::clone::Clone for CheckLoopVisitor<'tcx> {
    #[inline]
    fn clone(&self) -> CheckLoopVisitor<'tcx> {
        CheckLoopVisitor {
            tcx: ::core::clone::Clone::clone(&self.tcx),
            cx_stack: ::core::clone::Clone::clone(&self.cx_stack),
            block_breaks: ::core::clone::Clone::clone(&self.block_breaks),
        }
    }
}Clone)]
72struct CheckLoopVisitor<'tcx> {
73    tcx: TyCtxt<'tcx>,
74    // Keep track of a stack of contexts, so that suggestions
75    // are not made for contexts where it would be incorrect,
76    // such as adding a label for an `if`.
77    // e.g. `if 'foo: {}` would be incorrect.
78    cx_stack: Vec<Context>,
79    block_breaks: BTreeMap<Span, BlockInfo>,
80}
81
82pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir::Body<'tcx>) {
83    let mut check =
84        CheckLoopVisitor { tcx, cx_stack: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Normal]))vec![Normal], block_breaks: Default::default() };
85    let cx = match tcx.def_kind(def_id) {
86        DefKind::AnonConst => AnonConst,
87        _ => Fn,
88    };
89    check.with_context(cx, |v| v.visit_body(body));
90    check.report_outside_loop_error();
91}
92
93impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
94    type NestedFilter = nested_filter::OnlyBodies;
95
96    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
97        self.tcx
98    }
99
100    fn visit_anon_const(&mut self, _: &'hir hir::AnonConst) {
101        // Typecked on its own.
102    }
103
104    fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) {
105        self.with_context(ConstBlock, |v| intravisit::walk_inline_const(v, c));
106    }
107
108    fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
109        match e.kind {
110            hir::ExprKind::If(cond, then, else_opt) => {
111                self.visit_expr(cond);
112
113                let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
114                                 expr: &hir::Expr<'hir>|
115                 -> Option<&hir::Block<'hir>> {
116                    if let hir::ExprKind::Block(b, None) = expr.kind
117                        && #[allow(non_exhaustive_omitted_patterns)] match ck_loop.cx_stack.last() {
    Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_)) |
        Some(&UnlabeledIfBlock(_)) => true,
    _ => false,
}matches!(
118                            ck_loop.cx_stack.last(),
119                            Some(&Normal)
120                                | Some(&AnonConst)
121                                | Some(&UnlabeledBlock(_))
122                                | Some(&UnlabeledIfBlock(_))
123                        )
124                    {
125                        Some(b)
126                    } else {
127                        None
128                    }
129                };
130
131                if let Some(b) = get_block(self, then) {
132                    self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
133                        v.visit_block(b)
134                    });
135                } else {
136                    self.visit_expr(then);
137                }
138
139                if let Some(else_expr) = else_opt {
140                    if let Some(b) = get_block(self, else_expr) {
141                        self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
142                            v.visit_block(b)
143                        });
144                    } else {
145                        self.visit_expr(else_expr);
146                    }
147                }
148            }
149            hir::ExprKind::Loop(b, _, source, _) => {
150                let cx = match self.is_loop_match(e, b) {
151                    Some(labeled_block) => LoopMatch { labeled_block },
152                    None => Loop(source),
153                };
154
155                self.with_context(cx, |v| v.visit_block(b));
156            }
157            hir::ExprKind::Closure(&hir::Closure { fn_decl, body, fn_decl_span, kind, .. }) => {
158                let cx = match kind {
159                    hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
160                        Coroutine { coroutine_span: fn_decl_span, kind, source }
161                    }
162                    _ => Closure(fn_decl_span),
163                };
164                self.visit_fn_decl(fn_decl);
165                self.with_context(cx, |v| v.visit_nested_body(body));
166            }
167            hir::ExprKind::Block(b, Some(_label)) => {
168                self.with_context(LabeledBlock, |v| v.visit_block(b));
169            }
170            hir::ExprKind::Block(b, None)
171                if #[allow(non_exhaustive_omitted_patterns)] match self.cx_stack.last() {
    Some(&Fn) | Some(&ConstBlock) => true,
    _ => false,
}matches!(self.cx_stack.last(), Some(&Fn) | Some(&ConstBlock)) =>
172            {
173                self.with_context(Normal, |v| v.visit_block(b));
174            }
175            hir::ExprKind::Block(
176                b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
177                None,
178            ) if #[allow(non_exhaustive_omitted_patterns)] match self.cx_stack.last() {
    Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_)) => true,
    _ => false,
}matches!(
179                self.cx_stack.last(),
180                Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
181            ) =>
182            {
183                self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
184            }
185            hir::ExprKind::Break(break_destination, ref opt_expr) => {
186                if let Some(e) = opt_expr {
187                    self.visit_expr(e);
188                }
189
190                if self.require_label_in_labeled_block(e.span, &break_destination, "break") {
191                    // If we emitted an error about an unlabeled break in a labeled
192                    // block, we don't need any further checking for this break any more
193                    return;
194                }
195
196                let loop_id = match break_destination.target_id {
197                    Ok(loop_id) => Some(loop_id),
198                    Err(hir::LoopIdError::OutsideLoopScope) => None,
199                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
200                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
201                            span: e.span,
202                            cf_type: "break",
203                        });
204                        None
205                    }
206                    Err(hir::LoopIdError::UnresolvedLabel) => None,
207                };
208
209                // A `#[const_continue]` must break to a block in a `#[loop_match]`.
210                if {
        {
            'done:
                {
                for i in
                    ::rustc_hir::attrs::HasAttrs::get_attrs(e.hir_id, &self.tcx)
                    {
                    #[allow(unused_imports)]
                    use rustc_hir::attrs::AttributeKind::*;
                    let i: &rustc_hir::Attribute = i;
                    match i {
                        rustc_hir::Attribute::Parsed(ConstContinue(_)) => {
                            break 'done Some(());
                        }
                        rustc_hir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }.is_some()find_attr!(self.tcx, e.hir_id, ConstContinue(_)) {
211                    let Some(label) = break_destination.label else {
212                        let span = e.span;
213                        self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
214                    };
215
216                    let is_target_label = |cx: &Context| match cx {
217                        Context::LoopMatch { labeled_block } => {
218                            // NOTE: with macro expansion, the label's span might be different here
219                            // even though it does still refer to the same HIR node. A block
220                            // can't have two labels, so the hir_id is a unique identifier.
221                            if !labeled_block.target_id.is_ok() {
    ::core::panicking::panic("assertion failed: labeled_block.target_id.is_ok()")
};assert!(labeled_block.target_id.is_ok()); // see `is_loop_match`.
222                            break_destination.target_id == labeled_block.target_id
223                        }
224                        _ => false,
225                    };
226
227                    if !self.cx_stack.iter().rev().any(is_target_label) {
228                        let span = label.ident.span;
229                        self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
230                    }
231                }
232
233                if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
234                    return;
235                }
236
237                if let Some(break_expr) = opt_expr {
238                    let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
239                        match self.tcx.hir_expect_expr(loop_id).kind {
240                            hir::ExprKind::Loop(_, label, source, sp) => {
241                                (Some(sp), label, Some(source))
242                            }
243                            ref r => {
244                                ::rustc_middle::util::bug::span_bug_fmt(e.span,
    format_args!("break label resolved to a non-loop: {0:?}", r))span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
245                            }
246                        }
247                    } else {
248                        (None, None, None)
249                    };
250                    match loop_kind {
251                        None | Some(hir::LoopSource::Loop) => (),
252                        Some(kind) => {
253                            let suggestion = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("break{0}",
                break_destination.label.map_or_else(String::new,
                    |l|
                        ::alloc::__export::must_use({
                                ::alloc::fmt::format(format_args!(" {0}", l.ident))
                            }))))
    })format!(
254                                "break{}",
255                                break_destination
256                                    .label
257                                    .map_or_else(String::new, |l| format!(" {}", l.ident))
258                            );
259                            self.tcx.dcx().emit_err(BreakNonLoop {
260                                span: e.span,
261                                head,
262                                kind: kind.name(),
263                                suggestion,
264                                loop_label,
265                                break_label: break_destination.label,
266                                break_expr_kind: &break_expr.kind,
267                                break_expr_span: break_expr.span,
268                            });
269                        }
270                    }
271                }
272
273                let sp_lo = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(e.span)
274                    && let Some(break_pos) = snippet.find("break")
275                {
276                    e.span.with_lo(e.span.lo() + BytePos((break_pos + "break".len()) as u32))
277                } else {
278                    e.span.with_lo(e.span.lo() + BytePos("break".len() as u32))
279                };
280                let label_sp = match break_destination.label {
281                    Some(label) => sp_lo.with_hi(label.ident.span.hi()),
282                    None => sp_lo.shrink_to_lo(),
283                };
284                self.require_break_cx(
285                    BreakContextKind::Break,
286                    e.span,
287                    label_sp,
288                    self.cx_stack.len() - 1,
289                );
290            }
291            hir::ExprKind::Continue(destination) => {
292                self.require_label_in_labeled_block(e.span, &destination, "continue");
293
294                match destination.target_id {
295                    Ok(loop_id) => {
296                        if let Node::Block(block) = self.tcx.hir_node(loop_id) {
297                            self.tcx.dcx().emit_err(ContinueLabeledBlock {
298                                span: e.span,
299                                block_span: block.span,
300                            });
301                        }
302                    }
303                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
304                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
305                            span: e.span,
306                            cf_type: "continue",
307                        });
308                    }
309                    Err(_) => {}
310                }
311                self.require_break_cx(
312                    BreakContextKind::Continue,
313                    e.span,
314                    e.span,
315                    self.cx_stack.len() - 1,
316                )
317            }
318            _ => intravisit::walk_expr(self, e),
319        }
320    }
321}
322
323impl<'hir> CheckLoopVisitor<'hir> {
324    fn with_context<F>(&mut self, cx: Context, f: F)
325    where
326        F: FnOnce(&mut CheckLoopVisitor<'hir>),
327    {
328        self.cx_stack.push(cx);
329        f(self);
330        self.cx_stack.pop();
331    }
332
333    fn require_break_cx(
334        &mut self,
335        br_cx_kind: BreakContextKind,
336        span: Span,
337        break_span: Span,
338        cx_pos: usize,
339    ) {
340        match self.cx_stack[cx_pos] {
341            LabeledBlock | Loop(_) | LoopMatch { .. } => {}
342            Closure(closure_span) => {
343                self.tcx.dcx().emit_err(BreakInsideClosure {
344                    span,
345                    closure_span,
346                    name: &br_cx_kind.to_string(),
347                });
348            }
349            Coroutine { coroutine_span, kind, source } => {
350                let kind = match kind {
351                    hir::CoroutineDesugaring::Async => "async",
352                    hir::CoroutineDesugaring::Gen => "gen",
353                    hir::CoroutineDesugaring::AsyncGen => "async gen",
354                };
355                let source = match source {
356                    hir::CoroutineSource::Block => "block",
357                    hir::CoroutineSource::Closure => "closure",
358                    hir::CoroutineSource::Fn => "function",
359                };
360                self.tcx.dcx().emit_err(BreakInsideCoroutine {
361                    span,
362                    coroutine_span,
363                    name: &br_cx_kind.to_string(),
364                    kind,
365                    source,
366                });
367            }
368            UnlabeledBlock(block_span)
369                if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
370            {
371                let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
372                    name: br_cx_kind.to_string(),
373                    spans: ::alloc::vec::Vec::new()vec![],
374                    suggs: ::alloc::vec::Vec::new()vec![],
375                });
376                block.spans.push(span);
377                block.suggs.push(break_span);
378            }
379            UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
380                self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
381            }
382            Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
383                self.tcx.dcx().emit_err(OutsideLoop {
384                    spans: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [span]))vec![span],
385                    name: &br_cx_kind.to_string(),
386                    is_break: br_cx_kind == BreakContextKind::Break,
387                    suggestion: None,
388                });
389            }
390        }
391    }
392
393    fn require_label_in_labeled_block(
394        &self,
395        span: Span,
396        label: &Destination,
397        cf_type: &str,
398    ) -> bool {
399        if !span.is_desugaring(DesugaringKind::QuestionMark)
400            && self.cx_stack.last() == Some(&LabeledBlock)
401            && label.label.is_none()
402        {
403            self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
404            return true;
405        }
406        false
407    }
408
409    fn report_outside_loop_error(&self) {
410        for (s, block) in &self.block_breaks {
411            self.tcx.dcx().emit_err(OutsideLoop {
412                spans: block.spans.clone(),
413                name: &block.name,
414                is_break: true,
415                suggestion: Some(OutsideLoopSuggestion {
416                    block_span: *s,
417                    break_spans: block.suggs.clone(),
418                }),
419            });
420        }
421    }
422
423    /// Is this a loop annotated with `#[loop_match]` that looks syntactically sound?
424    fn is_loop_match(
425        &self,
426        e: &'hir hir::Expr<'hir>,
427        body: &'hir hir::Block<'hir>,
428    ) -> Option<Destination> {
429        if !{
        {
            'done:
                {
                for i in
                    ::rustc_hir::attrs::HasAttrs::get_attrs(e.hir_id, &self.tcx)
                    {
                    #[allow(unused_imports)]
                    use rustc_hir::attrs::AttributeKind::*;
                    let i: &rustc_hir::Attribute = i;
                    match i {
                        rustc_hir::Attribute::Parsed(LoopMatch(_)) => {
                            break 'done Some(());
                        }
                        rustc_hir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }.is_some()find_attr!(self.tcx, e.hir_id, LoopMatch(_)) {
430            return None;
431        }
432
433        // NOTE: Diagnostics are emitted during MIR construction.
434
435        // Accept either `state = expr` or `state = expr;`.
436        let loop_body_expr = match body.stmts {
437            [] => body.expr?,
438            [single] if body.expr.is_none() => match single.kind {
439                hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr,
440                _ => return None,
441            },
442            [..] => return None,
443        };
444
445        let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };
446
447        let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };
448
449        Some(Destination { label, target_id: Ok(block.hir_id) })
450    }
451}