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(ref 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 {
159 ref fn_decl, body, fn_decl_span, kind, ..
160 }) => {
161 let cx = match kind {
162 hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
163 Coroutine { coroutine_span: fn_decl_span, kind, source }
164 }
165 _ => Closure(fn_decl_span),
166 };
167 self.visit_fn_decl(fn_decl);
168 self.with_context(cx, |v| v.visit_nested_body(body));
169 }
170 hir::ExprKind::Block(ref b, Some(_label)) => {
171 self.with_context(LabeledBlock, |v| v.visit_block(b));
172 }
173 hir::ExprKind::Block(ref b, None)
174 if matches!(self.cx_stack.last(), Some(&Fn) | Some(&ConstBlock)) =>
175 {
176 self.with_context(Normal, |v| v.visit_block(b));
177 }
178 hir::ExprKind::Block(
179 ref b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
180 None,
181 ) if matches!(
182 self.cx_stack.last(),
183 Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
184 ) =>
185 {
186 self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
187 }
188 hir::ExprKind::Break(break_destination, ref opt_expr) => {
189 if let Some(e) = opt_expr {
190 self.visit_expr(e);
191 }
192
193 if self.require_label_in_labeled_block(e.span, &break_destination, "break") {
194 return;
197 }
198
199 let loop_id = match break_destination.target_id {
200 Ok(loop_id) => Some(loop_id),
201 Err(hir::LoopIdError::OutsideLoopScope) => None,
202 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
203 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
204 span: e.span,
205 cf_type: "break",
206 });
207 None
208 }
209 Err(hir::LoopIdError::UnresolvedLabel) => None,
210 };
211
212 if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) {
214 let Some(label) = break_destination.label else {
215 let span = e.span;
216 self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
217 };
218
219 let is_target_label = |cx: &Context| match cx {
220 Context::LoopMatch { labeled_block } => {
221 assert!(labeled_block.target_id.is_ok()); break_destination.target_id == labeled_block.target_id
226 }
227 _ => false,
228 };
229
230 if !self.cx_stack.iter().rev().any(is_target_label) {
231 let span = label.ident.span;
232 self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
233 }
234 }
235
236 if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
237 return;
238 }
239
240 if let Some(break_expr) = opt_expr {
241 let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
242 match self.tcx.hir_expect_expr(loop_id).kind {
243 hir::ExprKind::Loop(_, label, source, sp) => {
244 (Some(sp), label, Some(source))
245 }
246 ref r => {
247 span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
248 }
249 }
250 } else {
251 (None, None, None)
252 };
253 match loop_kind {
254 None | Some(hir::LoopSource::Loop) => (),
255 Some(kind) => {
256 let suggestion = format!(
257 "break{}",
258 break_destination
259 .label
260 .map_or_else(String::new, |l| format!(" {}", l.ident))
261 );
262 self.tcx.dcx().emit_err(BreakNonLoop {
263 span: e.span,
264 head,
265 kind: kind.name(),
266 suggestion,
267 loop_label,
268 break_label: break_destination.label,
269 break_expr_kind: &break_expr.kind,
270 break_expr_span: break_expr.span,
271 });
272 }
273 }
274 }
275
276 let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
277 let label_sp = match break_destination.label {
278 Some(label) => sp_lo.with_hi(label.ident.span.hi()),
279 None => sp_lo.shrink_to_lo(),
280 };
281 self.require_break_cx(
282 BreakContextKind::Break,
283 e.span,
284 label_sp,
285 self.cx_stack.len() - 1,
286 );
287 }
288 hir::ExprKind::Continue(destination) => {
289 self.require_label_in_labeled_block(e.span, &destination, "continue");
290
291 match destination.target_id {
292 Ok(loop_id) => {
293 if let Node::Block(block) = self.tcx.hir_node(loop_id) {
294 self.tcx.dcx().emit_err(ContinueLabeledBlock {
295 span: e.span,
296 block_span: block.span,
297 });
298 }
299 }
300 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
301 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
302 span: e.span,
303 cf_type: "continue",
304 });
305 }
306 Err(_) => {}
307 }
308 self.require_break_cx(
309 BreakContextKind::Continue,
310 e.span,
311 e.span,
312 self.cx_stack.len() - 1,
313 )
314 }
315 _ => intravisit::walk_expr(self, e),
316 }
317 }
318}
319
320impl<'hir> CheckLoopVisitor<'hir> {
321 fn with_context<F>(&mut self, cx: Context, f: F)
322 where
323 F: FnOnce(&mut CheckLoopVisitor<'hir>),
324 {
325 self.cx_stack.push(cx);
326 f(self);
327 self.cx_stack.pop();
328 }
329
330 fn require_break_cx(
331 &mut self,
332 br_cx_kind: BreakContextKind,
333 span: Span,
334 break_span: Span,
335 cx_pos: usize,
336 ) {
337 match self.cx_stack[cx_pos] {
338 LabeledBlock | Loop(_) | LoopMatch { .. } => {}
339 Closure(closure_span) => {
340 self.tcx.dcx().emit_err(BreakInsideClosure {
341 span,
342 closure_span,
343 name: &br_cx_kind.to_string(),
344 });
345 }
346 Coroutine { coroutine_span, kind, source } => {
347 let kind = match kind {
348 hir::CoroutineDesugaring::Async => "async",
349 hir::CoroutineDesugaring::Gen => "gen",
350 hir::CoroutineDesugaring::AsyncGen => "async gen",
351 };
352 let source = match source {
353 hir::CoroutineSource::Block => "block",
354 hir::CoroutineSource::Closure => "closure",
355 hir::CoroutineSource::Fn => "function",
356 };
357 self.tcx.dcx().emit_err(BreakInsideCoroutine {
358 span,
359 coroutine_span,
360 name: &br_cx_kind.to_string(),
361 kind,
362 source,
363 });
364 }
365 UnlabeledBlock(block_span)
366 if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
367 {
368 let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
369 name: br_cx_kind.to_string(),
370 spans: vec![],
371 suggs: vec![],
372 });
373 block.spans.push(span);
374 block.suggs.push(break_span);
375 }
376 UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
377 self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
378 }
379 Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
380 self.tcx.dcx().emit_err(OutsideLoop {
381 spans: vec![span],
382 name: &br_cx_kind.to_string(),
383 is_break: br_cx_kind == BreakContextKind::Break,
384 suggestion: None,
385 });
386 }
387 }
388 }
389
390 fn require_label_in_labeled_block(
391 &self,
392 span: Span,
393 label: &Destination,
394 cf_type: &str,
395 ) -> bool {
396 if !span.is_desugaring(DesugaringKind::QuestionMark)
397 && self.cx_stack.last() == Some(&LabeledBlock)
398 && label.label.is_none()
399 {
400 self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
401 return true;
402 }
403 false
404 }
405
406 fn report_outside_loop_error(&self) {
407 for (s, block) in &self.block_breaks {
408 self.tcx.dcx().emit_err(OutsideLoop {
409 spans: block.spans.clone(),
410 name: &block.name,
411 is_break: true,
412 suggestion: Some(OutsideLoopSuggestion {
413 block_span: *s,
414 break_spans: block.suggs.clone(),
415 }),
416 });
417 }
418 }
419
420 fn is_loop_match(
422 &self,
423 e: &'hir hir::Expr<'hir>,
424 body: &'hir hir::Block<'hir>,
425 ) -> Option<Destination> {
426 if !find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::LoopMatch(_)) {
427 return None;
428 }
429
430 let loop_body_expr = match body.stmts {
434 [] => match body.expr {
435 Some(expr) => expr,
436 None => return None,
437 },
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}