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