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