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::diagnostics::{
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        DefKind::InlineConst => {
88            // only type system inline consts are typeck roots
89            if true {
    if !tcx.is_type_system_inline_const(def_id) {
        ::core::panicking::panic("assertion failed: tcx.is_type_system_inline_const(def_id)")
    };
};debug_assert!(tcx.is_type_system_inline_const(def_id));
90            ConstBlock
91        }
92        _ => Fn,
93    };
94    check.with_context(cx, |v| v.visit_body(body));
95    check.report_outside_loop_error();
96}
97
98impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
99    type NestedFilter = nested_filter::OnlyBodies;
100
101    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
102        self.tcx
103    }
104
105    fn visit_anon_const(&mut self, _: &'hir hir::AnonConst) {
106        // Typecked on its own.
107    }
108
109    fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) {
110        self.with_context(ConstBlock, |v| intravisit::walk_inline_const(v, c));
111    }
112
113    fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
114        match e.kind {
115            hir::ExprKind::If(cond, then, else_opt) => {
116                self.visit_expr(cond);
117
118                let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
119                                 expr: &hir::Expr<'hir>|
120                 -> Option<&hir::Block<'hir>> {
121                    if let hir::ExprKind::Block(b, None) = expr.kind
122                        && #[allow(non_exhaustive_omitted_patterns)] match ck_loop.cx_stack.last() {
    Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_)) |
        Some(&UnlabeledIfBlock(_)) => true,
    _ => false,
}matches!(
123                            ck_loop.cx_stack.last(),
124                            Some(&Normal)
125                                | Some(&AnonConst)
126                                | Some(&UnlabeledBlock(_))
127                                | Some(&UnlabeledIfBlock(_))
128                        )
129                    {
130                        Some(b)
131                    } else {
132                        None
133                    }
134                };
135
136                if let Some(b) = get_block(self, then) {
137                    self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
138                        v.visit_block(b)
139                    });
140                } else {
141                    self.visit_expr(then);
142                }
143
144                if let Some(else_expr) = else_opt {
145                    if let Some(b) = get_block(self, else_expr) {
146                        self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
147                            v.visit_block(b)
148                        });
149                    } else {
150                        self.visit_expr(else_expr);
151                    }
152                }
153            }
154            hir::ExprKind::Loop(b, _, source, _) => {
155                let cx = match self.is_loop_match(e, b) {
156                    Some(labeled_block) => LoopMatch { labeled_block },
157                    None => Loop(source),
158                };
159
160                self.with_context(cx, |v| v.visit_block(b));
161            }
162            hir::ExprKind::Closure(&hir::Closure { fn_decl, body, fn_decl_span, kind, .. }) => {
163                let cx = match kind {
164                    hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
165                        Coroutine { coroutine_span: fn_decl_span, kind, source }
166                    }
167                    _ => Closure(fn_decl_span),
168                };
169                self.visit_fn_decl(fn_decl);
170                self.with_context(cx, |v| v.visit_nested_body(body));
171            }
172            hir::ExprKind::Block(b, Some(_label)) => {
173                self.with_context(LabeledBlock, |v| v.visit_block(b));
174            }
175            hir::ExprKind::Block(b, None)
176                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)) =>
177            {
178                self.with_context(Normal, |v| v.visit_block(b));
179            }
180            hir::ExprKind::Block(
181                b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
182                None,
183            ) if #[allow(non_exhaustive_omitted_patterns)] match self.cx_stack.last() {
    Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_)) => true,
    _ => false,
}matches!(
184                self.cx_stack.last(),
185                Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
186            ) =>
187            {
188                self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
189            }
190            hir::ExprKind::Break(break_destination, ref opt_expr) => {
191                if let Some(e) = opt_expr {
192                    self.visit_expr(e);
193                }
194
195                if self.require_label_in_labeled_block(e.span, &break_destination, "break") {
196                    // If we emitted an error about an unlabeled break in a labeled
197                    // block, we don't need any further checking for this break any more
198                    return;
199                }
200
201                let loop_id = match break_destination.target_id {
202                    Ok(loop_id) => Some(loop_id),
203                    Err(hir::LoopIdError::OutsideLoopScope) => None,
204                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
205                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
206                            span: e.span,
207                            cf_type: "break",
208                        });
209                        None
210                    }
211                    Err(hir::LoopIdError::UnresolvedLabel) => None,
212                };
213
214                // A `#[const_continue]` must break to a block in a `#[loop_match]`.
215                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(_)) {
216                    let Some(label) = break_destination.label else {
217                        let span = e.span;
218                        self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
219                    };
220
221                    let is_target_label = |cx: &Context| match cx {
222                        Context::LoopMatch { labeled_block } => {
223                            // NOTE: with macro expansion, the label's span might be different here
224                            // even though it does still refer to the same HIR node. A block
225                            // can't have two labels, so the hir_id is a unique identifier.
226                            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`.
227                            break_destination.target_id == labeled_block.target_id
228                        }
229                        _ => false,
230                    };
231
232                    if !self.cx_stack.iter().rev().any(is_target_label) {
233                        let span = label.ident.span;
234                        self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
235                    }
236                }
237
238                if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
239                    return;
240                }
241
242                if let Some(break_expr) = opt_expr {
243                    let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
244                        match self.tcx.hir_expect_expr(loop_id).kind {
245                            hir::ExprKind::Loop(_, label, source, sp) => {
246                                (Some(sp), label, Some(source))
247                            }
248                            ref r => {
249                                ::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)
250                            }
251                        }
252                    } else {
253                        (None, None, None)
254                    };
255                    match loop_kind {
256                        None | Some(hir::LoopSource::Loop) => (),
257                        Some(kind) => {
258                            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!(
259                                "break{}",
260                                break_destination
261                                    .label
262                                    .map_or_else(String::new, |l| format!(" {}", l.ident))
263                            );
264                            self.tcx.dcx().emit_err(BreakNonLoop {
265                                span: e.span,
266                                head,
267                                kind: kind.name(),
268                                suggestion,
269                                loop_label,
270                                break_label: break_destination.label,
271                                break_expr_kind: &break_expr.kind,
272                                break_expr_span: break_expr.span,
273                            });
274                        }
275                    }
276                }
277
278                let sp_lo = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(e.span)
279                    && let Some(break_pos) = snippet.find("break")
280                {
281                    e.span.with_lo(e.span.lo() + BytePos((break_pos + "break".len()) as u32))
282                } else {
283                    e.span.with_lo(e.span.lo() + BytePos("break".len() as u32))
284                };
285                let label_sp = match break_destination.label {
286                    Some(label) => sp_lo.with_hi(label.ident.span.hi()),
287                    None => sp_lo.shrink_to_lo(),
288                };
289                self.require_break_cx(
290                    BreakContextKind::Break,
291                    e.span,
292                    label_sp,
293                    self.cx_stack.len() - 1,
294                );
295            }
296            hir::ExprKind::Continue(destination) => {
297                self.require_label_in_labeled_block(e.span, &destination, "continue");
298
299                match destination.target_id {
300                    Ok(loop_id) => {
301                        if let Node::Block(block) = self.tcx.hir_node(loop_id) {
302                            self.tcx.dcx().emit_err(ContinueLabeledBlock {
303                                span: e.span,
304                                block_span: block.span,
305                            });
306                        }
307                    }
308                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
309                        self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
310                            span: e.span,
311                            cf_type: "continue",
312                        });
313                    }
314                    Err(_) => {}
315                }
316                self.require_break_cx(
317                    BreakContextKind::Continue,
318                    e.span,
319                    e.span,
320                    self.cx_stack.len() - 1,
321                )
322            }
323            _ => intravisit::walk_expr(self, e),
324        }
325    }
326}
327
328impl<'hir> CheckLoopVisitor<'hir> {
329    fn with_context<F>(&mut self, cx: Context, f: F)
330    where
331        F: FnOnce(&mut CheckLoopVisitor<'hir>),
332    {
333        self.cx_stack.push(cx);
334        f(self);
335        self.cx_stack.pop();
336    }
337
338    fn require_break_cx(
339        &mut self,
340        br_cx_kind: BreakContextKind,
341        span: Span,
342        break_span: Span,
343        cx_pos: usize,
344    ) {
345        match self.cx_stack[cx_pos] {
346            LabeledBlock | Loop(_) | LoopMatch { .. } => {}
347            Closure(closure_span) => {
348                self.tcx.dcx().emit_err(BreakInsideClosure {
349                    span,
350                    closure_span,
351                    name: &br_cx_kind.to_string(),
352                });
353            }
354            Coroutine { coroutine_span, kind, source } => {
355                let kind = match kind {
356                    hir::CoroutineDesugaring::Async => "async",
357                    hir::CoroutineDesugaring::Gen => "gen",
358                    hir::CoroutineDesugaring::AsyncGen => "async gen",
359                };
360                let source = match source {
361                    hir::CoroutineSource::Block => "block",
362                    hir::CoroutineSource::Closure => "closure",
363                    hir::CoroutineSource::Fn => "function",
364                };
365                self.tcx.dcx().emit_err(BreakInsideCoroutine {
366                    span,
367                    coroutine_span,
368                    name: &br_cx_kind.to_string(),
369                    kind,
370                    source,
371                });
372            }
373            UnlabeledBlock(block_span)
374                if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
375            {
376                let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
377                    name: br_cx_kind.to_string(),
378                    spans: ::alloc::vec::Vec::new()vec![],
379                    suggs: ::alloc::vec::Vec::new()vec![],
380                });
381                block.spans.push(span);
382                block.suggs.push(break_span);
383            }
384            UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
385                self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
386            }
387            Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
388                self.tcx.dcx().emit_err(OutsideLoop {
389                    spans: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [span]))vec![span],
390                    name: &br_cx_kind.to_string(),
391                    is_break: br_cx_kind == BreakContextKind::Break,
392                    suggestion: None,
393                });
394            }
395        }
396    }
397
398    fn require_label_in_labeled_block(
399        &self,
400        span: Span,
401        label: &Destination,
402        cf_type: &str,
403    ) -> bool {
404        if !span.is_desugaring(DesugaringKind::QuestionMark)
405            && self.cx_stack.last() == Some(&LabeledBlock)
406            && label.label.is_none()
407        {
408            self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
409            return true;
410        }
411        false
412    }
413
414    fn report_outside_loop_error(&self) {
415        for (s, block) in &self.block_breaks {
416            self.tcx.dcx().emit_err(OutsideLoop {
417                spans: block.spans.clone(),
418                name: &block.name,
419                is_break: true,
420                suggestion: Some(OutsideLoopSuggestion {
421                    block_span: *s,
422                    break_spans: block.suggs.clone(),
423                }),
424            });
425        }
426    }
427
428    /// Is this a loop annotated with `#[loop_match]` that looks syntactically sound?
429    fn is_loop_match(
430        &self,
431        e: &'hir hir::Expr<'hir>,
432        body: &'hir hir::Block<'hir>,
433    ) -> Option<Destination> {
434        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(_)) {
435            return None;
436        }
437
438        // NOTE: Diagnostics are emitted during MIR construction.
439
440        // Accept either `state = expr` or `state = expr;`.
441        let loop_body_expr = match body.stmts {
442            [] => body.expr?,
443            [single] if body.expr.is_none() => match single.kind {
444                hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr,
445                _ => return None,
446            },
447            [..] => return None,
448        };
449
450        let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };
451
452        let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };
453
454        Some(Destination { label, target_id: Ok(block.hir_id) })
455    }
456}