1use std::collections::BTreeMap;
2use std::fmt;
3
4use Context::*;
5use rustc_hir as hir;
6use rustc_hir::attrs::AttributeKind;
7use rustc_hir::def::DefKind;
8use rustc_hir::def_id::LocalDefId;
9use rustc_hir::intravisit::{self, Visitor};
10use rustc_hir::{Destination, Node, find_attr};
11use rustc_middle::hir::nested_filter;
12use rustc_middle::span_bug;
13use rustc_middle::ty::TyCtxt;
14use rustc_span::hygiene::DesugaringKind;
15use rustc_span::{BytePos, Span};
16
17use crate::errors::{
18 BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ConstContinueBadLabel,
19 ContinueLabeledBlock, OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition,
20 UnlabeledInLabeledBlock,
21};
22
23#[derive(Clone, Copy, Debug, PartialEq)]
25enum Context {
26 Normal,
27 Fn,
28 Loop(hir::LoopSource),
29 Closure(Span),
30 Coroutine {
31 coroutine_span: Span,
32 kind: hir::CoroutineDesugaring,
33 source: hir::CoroutineSource,
34 },
35 UnlabeledBlock(Span),
36 UnlabeledIfBlock(Span),
37 LabeledBlock,
38 AnonConst,
40 ConstBlock,
42 LoopMatch {
44 labeled_block: Destination,
46 },
47}
48
49#[derive(Clone)]
50struct BlockInfo {
51 name: String,
52 spans: Vec<Span>,
53 suggs: Vec<Span>,
54}
55
56#[derive(PartialEq)]
57enum BreakContextKind {
58 Break,
59 Continue,
60}
61
62impl fmt::Display for BreakContextKind {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 BreakContextKind::Break => "break",
66 BreakContextKind::Continue => "continue",
67 }
68 .fmt(f)
69 }
70}
71
72#[derive(Clone)]
73struct CheckLoopVisitor<'tcx> {
74 tcx: TyCtxt<'tcx>,
75 cx_stack: Vec<Context>,
80 block_breaks: BTreeMap<Span, BlockInfo>,
81}
82
83pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir::Body<'tcx>) {
84 let mut check =
85 CheckLoopVisitor { tcx, cx_stack: vec![Normal], block_breaks: Default::default() };
86 let cx = match tcx.def_kind(def_id) {
87 DefKind::AnonConst => AnonConst,
88 _ => Fn,
89 };
90 check.with_context(cx, |v| v.visit_body(body));
91 check.report_outside_loop_error();
92}
93
94impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
95 type NestedFilter = nested_filter::OnlyBodies;
96
97 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
98 self.tcx
99 }
100
101 fn visit_anon_const(&mut self, _: &'hir hir::AnonConst) {
102 }
104
105 fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) {
106 self.with_context(ConstBlock, |v| intravisit::walk_inline_const(v, c));
107 }
108
109 fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
110 match e.kind {
111 hir::ExprKind::If(cond, then, else_opt) => {
112 self.visit_expr(cond);
113
114 let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
115 expr: &hir::Expr<'hir>|
116 -> Option<&hir::Block<'hir>> {
117 if let hir::ExprKind::Block(b, None) = expr.kind
118 && matches!(
119 ck_loop.cx_stack.last(),
120 Some(&Normal)
121 | Some(&AnonConst)
122 | Some(&UnlabeledBlock(_))
123 | Some(&UnlabeledIfBlock(_))
124 )
125 {
126 Some(b)
127 } else {
128 None
129 }
130 };
131
132 if let Some(b) = get_block(self, then) {
133 self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
134 v.visit_block(b)
135 });
136 } else {
137 self.visit_expr(then);
138 }
139
140 if let Some(else_expr) = else_opt {
141 if let Some(b) = get_block(self, else_expr) {
142 self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
143 v.visit_block(b)
144 });
145 } else {
146 self.visit_expr(else_expr);
147 }
148 }
149 }
150 hir::ExprKind::Loop(b, _, source, _) => {
151 let cx = match self.is_loop_match(e, b) {
152 Some(labeled_block) => LoopMatch { labeled_block },
153 None => Loop(source),
154 };
155
156 self.with_context(cx, |v| v.visit_block(b));
157 }
158 hir::ExprKind::Closure(&hir::Closure { fn_decl, body, fn_decl_span, kind, .. }) => {
159 let cx = match kind {
160 hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
161 Coroutine { coroutine_span: fn_decl_span, kind, source }
162 }
163 _ => Closure(fn_decl_span),
164 };
165 self.visit_fn_decl(fn_decl);
166 self.with_context(cx, |v| v.visit_nested_body(body));
167 }
168 hir::ExprKind::Block(b, Some(_label)) => {
169 self.with_context(LabeledBlock, |v| v.visit_block(b));
170 }
171 hir::ExprKind::Block(b, None)
172 if matches!(self.cx_stack.last(), Some(&Fn) | Some(&ConstBlock)) =>
173 {
174 self.with_context(Normal, |v| v.visit_block(b));
175 }
176 hir::ExprKind::Block(
177 b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
178 None,
179 ) if matches!(
180 self.cx_stack.last(),
181 Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
182 ) =>
183 {
184 self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
185 }
186 hir::ExprKind::Break(break_destination, ref opt_expr) => {
187 if let Some(e) = opt_expr {
188 self.visit_expr(e);
189 }
190
191 if self.require_label_in_labeled_block(e.span, &break_destination, "break") {
192 return;
195 }
196
197 let loop_id = match break_destination.target_id {
198 Ok(loop_id) => Some(loop_id),
199 Err(hir::LoopIdError::OutsideLoopScope) => None,
200 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
201 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
202 span: e.span,
203 cf_type: "break",
204 });
205 None
206 }
207 Err(hir::LoopIdError::UnresolvedLabel) => None,
208 };
209
210 if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) {
212 let Some(label) = break_destination.label else {
213 let span = e.span;
214 self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
215 };
216
217 let is_target_label = |cx: &Context| match cx {
218 Context::LoopMatch { labeled_block } => {
219 assert!(labeled_block.target_id.is_ok()); break_destination.target_id == labeled_block.target_id
224 }
225 _ => false,
226 };
227
228 if !self.cx_stack.iter().rev().any(is_target_label) {
229 let span = label.ident.span;
230 self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
231 }
232 }
233
234 if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
235 return;
236 }
237
238 if let Some(break_expr) = opt_expr {
239 let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
240 match self.tcx.hir_expect_expr(loop_id).kind {
241 hir::ExprKind::Loop(_, label, source, sp) => {
242 (Some(sp), label, Some(source))
243 }
244 ref r => {
245 span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
246 }
247 }
248 } else {
249 (None, None, None)
250 };
251 match loop_kind {
252 None | Some(hir::LoopSource::Loop) => (),
253 Some(kind) => {
254 let suggestion = format!(
255 "break{}",
256 break_destination
257 .label
258 .map_or_else(String::new, |l| format!(" {}", l.ident))
259 );
260 self.tcx.dcx().emit_err(BreakNonLoop {
261 span: e.span,
262 head,
263 kind: kind.name(),
264 suggestion,
265 loop_label,
266 break_label: break_destination.label,
267 break_expr_kind: &break_expr.kind,
268 break_expr_span: break_expr.span,
269 });
270 }
271 }
272 }
273
274 let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
275 let label_sp = match break_destination.label {
276 Some(label) => sp_lo.with_hi(label.ident.span.hi()),
277 None => sp_lo.shrink_to_lo(),
278 };
279 self.require_break_cx(
280 BreakContextKind::Break,
281 e.span,
282 label_sp,
283 self.cx_stack.len() - 1,
284 );
285 }
286 hir::ExprKind::Continue(destination) => {
287 self.require_label_in_labeled_block(e.span, &destination, "continue");
288
289 match destination.target_id {
290 Ok(loop_id) => {
291 if let Node::Block(block) = self.tcx.hir_node(loop_id) {
292 self.tcx.dcx().emit_err(ContinueLabeledBlock {
293 span: e.span,
294 block_span: block.span,
295 });
296 }
297 }
298 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
299 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
300 span: e.span,
301 cf_type: "continue",
302 });
303 }
304 Err(_) => {}
305 }
306 self.require_break_cx(
307 BreakContextKind::Continue,
308 e.span,
309 e.span,
310 self.cx_stack.len() - 1,
311 )
312 }
313 _ => intravisit::walk_expr(self, e),
314 }
315 }
316}
317
318impl<'hir> CheckLoopVisitor<'hir> {
319 fn with_context<F>(&mut self, cx: Context, f: F)
320 where
321 F: FnOnce(&mut CheckLoopVisitor<'hir>),
322 {
323 self.cx_stack.push(cx);
324 f(self);
325 self.cx_stack.pop();
326 }
327
328 fn require_break_cx(
329 &mut self,
330 br_cx_kind: BreakContextKind,
331 span: Span,
332 break_span: Span,
333 cx_pos: usize,
334 ) {
335 match self.cx_stack[cx_pos] {
336 LabeledBlock | Loop(_) | LoopMatch { .. } => {}
337 Closure(closure_span) => {
338 self.tcx.dcx().emit_err(BreakInsideClosure {
339 span,
340 closure_span,
341 name: &br_cx_kind.to_string(),
342 });
343 }
344 Coroutine { coroutine_span, kind, source } => {
345 let kind = match kind {
346 hir::CoroutineDesugaring::Async => "async",
347 hir::CoroutineDesugaring::Gen => "gen",
348 hir::CoroutineDesugaring::AsyncGen => "async gen",
349 };
350 let source = match source {
351 hir::CoroutineSource::Block => "block",
352 hir::CoroutineSource::Closure => "closure",
353 hir::CoroutineSource::Fn => "function",
354 };
355 self.tcx.dcx().emit_err(BreakInsideCoroutine {
356 span,
357 coroutine_span,
358 name: &br_cx_kind.to_string(),
359 kind,
360 source,
361 });
362 }
363 UnlabeledBlock(block_span)
364 if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
365 {
366 let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
367 name: br_cx_kind.to_string(),
368 spans: vec![],
369 suggs: vec![],
370 });
371 block.spans.push(span);
372 block.suggs.push(break_span);
373 }
374 UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
375 self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
376 }
377 Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
378 self.tcx.dcx().emit_err(OutsideLoop {
379 spans: vec![span],
380 name: &br_cx_kind.to_string(),
381 is_break: br_cx_kind == BreakContextKind::Break,
382 suggestion: None,
383 });
384 }
385 }
386 }
387
388 fn require_label_in_labeled_block(
389 &self,
390 span: Span,
391 label: &Destination,
392 cf_type: &str,
393 ) -> bool {
394 if !span.is_desugaring(DesugaringKind::QuestionMark)
395 && self.cx_stack.last() == Some(&LabeledBlock)
396 && label.label.is_none()
397 {
398 self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
399 return true;
400 }
401 false
402 }
403
404 fn report_outside_loop_error(&self) {
405 for (s, block) in &self.block_breaks {
406 self.tcx.dcx().emit_err(OutsideLoop {
407 spans: block.spans.clone(),
408 name: &block.name,
409 is_break: true,
410 suggestion: Some(OutsideLoopSuggestion {
411 block_span: *s,
412 break_spans: block.suggs.clone(),
413 }),
414 });
415 }
416 }
417
418 fn is_loop_match(
420 &self,
421 e: &'hir hir::Expr<'hir>,
422 body: &'hir hir::Block<'hir>,
423 ) -> Option<Destination> {
424 if !find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::LoopMatch(_)) {
425 return None;
426 }
427
428 let loop_body_expr = match body.stmts {
432 [] => body.expr?,
433 [single] if body.expr.is_none() => match single.kind {
434 hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr,
435 _ => return None,
436 },
437 [..] => return None,
438 };
439
440 let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };
441
442 let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };
443
444 Some(Destination { label, target_id: Ok(block.hir_id) })
445 }
446}