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 self.tcx.hir_attrs(e.hir_id) {
                    #[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.hir_attrs(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 = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
274                let label_sp = match break_destination.label {
275                    Some(label) => sp_lo.with_hi(label.ident.span.hi()),
276                    None => sp_lo.shrink_to_lo(),
277                };
278                self.require_break_cx(
279                    BreakContextKind::Break,
280                    e.span,
281                    label_sp,
282                    self.cx_stack.len() - 1,
283                );
284            }
285            hir::ExprKind::Continue(destination) => {
286                self.require_label_in_labeled_block(e.span, &destination, "continue");
287
288                match destination.target_id {
289                    Ok(loop_id) => {
290                        if let Node::Block(block) = self.tcx.hir_node(loop_id) {
291                            self.tcx.dcx().emit_err(ContinueLabeledBlock {
292                                span: e.span,
293                                block_span: block.span,
294                            });
295                        }
296                    }
297                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
298                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
299                            span: e.span,
300                            cf_type: "continue",
301                        });
302                    }
303                    Err(_) => {}
304                }
305                self.require_break_cx(
306                    BreakContextKind::Continue,
307                    e.span,
308                    e.span,
309                    self.cx_stack.len() - 1,
310                )
311            }
312            _ => intravisit::walk_expr(self, e),
313        }
314    }
315}
316
317impl<'hir> CheckLoopVisitor<'hir> {
318    fn with_context<F>(&mut self, cx: Context, f: F)
319    where
320        F: FnOnce(&mut CheckLoopVisitor<'hir>),
321    {
322        self.cx_stack.push(cx);
323        f(self);
324        self.cx_stack.pop();
325    }
326
327    fn require_break_cx(
328        &mut self,
329        br_cx_kind: BreakContextKind,
330        span: Span,
331        break_span: Span,
332        cx_pos: usize,
333    ) {
334        match self.cx_stack[cx_pos] {
335            LabeledBlock | Loop(_) | LoopMatch { .. } => {}
336            Closure(closure_span) => {
337                self.tcx.dcx().emit_err(BreakInsideClosure {
338                    span,
339                    closure_span,
340                    name: &br_cx_kind.to_string(),
341                });
342            }
343            Coroutine { coroutine_span, kind, source } => {
344                let kind = match kind {
345                    hir::CoroutineDesugaring::Async => "async",
346                    hir::CoroutineDesugaring::Gen => "gen",
347                    hir::CoroutineDesugaring::AsyncGen => "async gen",
348                };
349                let source = match source {
350                    hir::CoroutineSource::Block => "block",
351                    hir::CoroutineSource::Closure => "closure",
352                    hir::CoroutineSource::Fn => "function",
353                };
354                self.tcx.dcx().emit_err(BreakInsideCoroutine {
355                    span,
356                    coroutine_span,
357                    name: &br_cx_kind.to_string(),
358                    kind,
359                    source,
360                });
361            }
362            UnlabeledBlock(block_span)
363                if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
364            {
365                let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
366                    name: br_cx_kind.to_string(),
367                    spans: ::alloc::vec::Vec::new()vec![],
368                    suggs: ::alloc::vec::Vec::new()vec![],
369                });
370                block.spans.push(span);
371                block.suggs.push(break_span);
372            }
373            UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
374                self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
375            }
376            Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
377                self.tcx.dcx().emit_err(OutsideLoop {
378                    spans: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [span]))vec![span],
379                    name: &br_cx_kind.to_string(),
380                    is_break: br_cx_kind == BreakContextKind::Break,
381                    suggestion: None,
382                });
383            }
384        }
385    }
386
387    fn require_label_in_labeled_block(
388        &self,
389        span: Span,
390        label: &Destination,
391        cf_type: &str,
392    ) -> bool {
393        if !span.is_desugaring(DesugaringKind::QuestionMark)
394            && self.cx_stack.last() == Some(&LabeledBlock)
395            && label.label.is_none()
396        {
397            self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
398            return true;
399        }
400        false
401    }
402
403    fn report_outside_loop_error(&self) {
404        for (s, block) in &self.block_breaks {
405            self.tcx.dcx().emit_err(OutsideLoop {
406                spans: block.spans.clone(),
407                name: &block.name,
408                is_break: true,
409                suggestion: Some(OutsideLoopSuggestion {
410                    block_span: *s,
411                    break_spans: block.suggs.clone(),
412                }),
413            });
414        }
415    }
416
417    /// Is this a loop annotated with `#[loop_match]` that looks syntactically sound?
418    fn is_loop_match(
419        &self,
420        e: &'hir hir::Expr<'hir>,
421        body: &'hir hir::Block<'hir>,
422    ) -> Option<Destination> {
423        if !{
    {
            'done:
                {
                for i in self.tcx.hir_attrs(e.hir_id) {
                    #[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.hir_attrs(e.hir_id), LoopMatch(_)) {
424            return None;
425        }
426
427        // NOTE: Diagnostics are emitted during MIR construction.
428
429        // Accept either `state = expr` or `state = expr;`.
430        let loop_body_expr = match body.stmts {
431            [] => body.expr?,
432            [single] if body.expr.is_none() => match single.kind {
433                hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr,
434                _ => return None,
435            },
436            [..] => return None,
437        };
438
439        let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };
440
441        let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };
442
443        Some(Destination { label, target_id: Ok(block.hir_id) })
444    }
445}