1use std::collections::BTreeMap;
2use std::fmt;
3
4use Context::*;
5use rustc_hir as hir;
6use rustc_hir::def_id::{LocalDefId, LocalModDefId};
7use rustc_hir::intravisit::{self, Visitor};
8use rustc_hir::{Destination, Node};
9use rustc_middle::hir::nested_filter;
10use rustc_middle::query::Providers;
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
76fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
77 let mut check =
78 CheckLoopVisitor { tcx, cx_stack: vec![Normal], block_breaks: Default::default() };
79 tcx.hir().visit_item_likes_in_module(module_def_id, &mut check);
80 check.report_outside_loop_error();
81}
82
83pub(crate) fn provide(providers: &mut Providers) {
84 *providers = Providers { check_mod_loops, ..*providers };
85}
86
87impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
88 type NestedFilter = nested_filter::OnlyBodies;
89
90 fn nested_visit_map(&mut self) -> Self::Map {
91 self.tcx.hir()
92 }
93
94 fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
95 self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
96 }
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_fn(
103 &mut self,
104 fk: hir::intravisit::FnKind<'hir>,
105 fd: &'hir hir::FnDecl<'hir>,
106 b: hir::BodyId,
107 _: Span,
108 id: LocalDefId,
109 ) {
110 self.with_context(Fn, |v| intravisit::walk_fn(v, fk, fd, b, id));
111 }
112
113 fn visit_trait_item(&mut self, trait_item: &'hir hir::TraitItem<'hir>) {
114 self.with_context(Fn, |v| intravisit::walk_trait_item(v, trait_item));
115 }
116
117 fn visit_impl_item(&mut self, impl_item: &'hir hir::ImplItem<'hir>) {
118 self.with_context(Fn, |v| intravisit::walk_impl_item(v, impl_item));
119 }
120
121 fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
122 match e.kind {
123 hir::ExprKind::If(cond, then, else_opt) => {
124 self.visit_expr(cond);
125
126 let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
127 expr: &hir::Expr<'hir>|
128 -> Option<&hir::Block<'hir>> {
129 if let hir::ExprKind::Block(b, None) = expr.kind
130 && matches!(
131 ck_loop.cx_stack.last(),
132 Some(&Normal)
133 | Some(&AnonConst)
134 | Some(&UnlabeledBlock(_))
135 | Some(&UnlabeledIfBlock(_))
136 )
137 {
138 Some(b)
139 } else {
140 None
141 }
142 };
143
144 if let Some(b) = get_block(self, then) {
145 self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
146 v.visit_block(b)
147 });
148 } else {
149 self.visit_expr(then);
150 }
151
152 if let Some(else_expr) = else_opt {
153 if let Some(b) = get_block(self, else_expr) {
154 self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
155 v.visit_block(b)
156 });
157 } else {
158 self.visit_expr(else_expr);
159 }
160 }
161 }
162 hir::ExprKind::Loop(ref b, _, source, _) => {
163 self.with_context(Loop(source), |v| v.visit_block(b));
164 }
165 hir::ExprKind::Closure(&hir::Closure {
166 ref fn_decl, body, fn_decl_span, kind, ..
167 }) => {
168 let cx = match kind {
169 hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(kind, source)) => {
170 Coroutine { coroutine_span: fn_decl_span, kind, source }
171 }
172 _ => Closure(fn_decl_span),
173 };
174 self.visit_fn_decl(fn_decl);
175 self.with_context(cx, |v| v.visit_nested_body(body));
176 }
177 hir::ExprKind::Block(ref b, Some(_label)) => {
178 self.with_context(LabeledBlock, |v| v.visit_block(b));
179 }
180 hir::ExprKind::Block(ref b, None)
181 if matches!(self.cx_stack.last(), Some(&Fn) | Some(&ConstBlock)) =>
182 {
183 self.with_context(Normal, |v| v.visit_block(b));
184 }
185 hir::ExprKind::Block(
186 ref b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
187 None,
188 ) if matches!(
189 self.cx_stack.last(),
190 Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock(_))
191 ) =>
192 {
193 self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
194 }
195 hir::ExprKind::Break(break_label, ref opt_expr) => {
196 if let Some(e) = opt_expr {
197 self.visit_expr(e);
198 }
199
200 if self.require_label_in_labeled_block(e.span, &break_label, "break") {
201 return;
204 }
205
206 let loop_id = match break_label.target_id {
207 Ok(loop_id) => Some(loop_id),
208 Err(hir::LoopIdError::OutsideLoopScope) => None,
209 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
210 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
211 span: e.span,
212 cf_type: "break",
213 });
214 None
215 }
216 Err(hir::LoopIdError::UnresolvedLabel) => None,
217 };
218
219 if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
220 return;
221 }
222
223 if let Some(break_expr) = opt_expr {
224 let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
225 match self.tcx.hir().expect_expr(loop_id).kind {
226 hir::ExprKind::Loop(_, label, source, sp) => {
227 (Some(sp), label, Some(source))
228 }
229 ref r => {
230 span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
231 }
232 }
233 } else {
234 (None, None, None)
235 };
236 match loop_kind {
237 None | Some(hir::LoopSource::Loop) => (),
238 Some(kind) => {
239 let suggestion = format!(
240 "break{}",
241 break_label
242 .label
243 .map_or_else(String::new, |l| format!(" {}", l.ident))
244 );
245 self.tcx.dcx().emit_err(BreakNonLoop {
246 span: e.span,
247 head,
248 kind: kind.name(),
249 suggestion,
250 loop_label,
251 break_label: break_label.label,
252 break_expr_kind: &break_expr.kind,
253 break_expr_span: break_expr.span,
254 });
255 }
256 }
257 }
258
259 let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
260 let label_sp = match break_label.label {
261 Some(label) => sp_lo.with_hi(label.ident.span.hi()),
262 None => sp_lo.shrink_to_lo(),
263 };
264 self.require_break_cx(
265 BreakContextKind::Break,
266 e.span,
267 label_sp,
268 self.cx_stack.len() - 1,
269 );
270 }
271 hir::ExprKind::Continue(destination) => {
272 self.require_label_in_labeled_block(e.span, &destination, "continue");
273
274 match destination.target_id {
275 Ok(loop_id) => {
276 if let Node::Block(block) = self.tcx.hir_node(loop_id) {
277 self.tcx.dcx().emit_err(ContinueLabeledBlock {
278 span: e.span,
279 block_span: block.span,
280 });
281 }
282 }
283 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
284 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
285 span: e.span,
286 cf_type: "continue",
287 });
288 }
289 Err(_) => {}
290 }
291 self.require_break_cx(
292 BreakContextKind::Continue,
293 e.span,
294 e.span,
295 self.cx_stack.len() - 1,
296 )
297 }
298 _ => intravisit::walk_expr(self, e),
299 }
300 }
301}
302
303impl<'hir> CheckLoopVisitor<'hir> {
304 fn with_context<F>(&mut self, cx: Context, f: F)
305 where
306 F: FnOnce(&mut CheckLoopVisitor<'hir>),
307 {
308 self.cx_stack.push(cx);
309 f(self);
310 self.cx_stack.pop();
311 }
312
313 fn require_break_cx(
314 &mut self,
315 br_cx_kind: BreakContextKind,
316 span: Span,
317 break_span: Span,
318 cx_pos: usize,
319 ) {
320 match self.cx_stack[cx_pos] {
321 LabeledBlock | Loop(_) => {}
322 Closure(closure_span) => {
323 self.tcx.dcx().emit_err(BreakInsideClosure {
324 span,
325 closure_span,
326 name: &br_cx_kind.to_string(),
327 });
328 }
329 Coroutine { coroutine_span, kind, source } => {
330 let kind = match kind {
331 hir::CoroutineDesugaring::Async => "async",
332 hir::CoroutineDesugaring::Gen => "gen",
333 hir::CoroutineDesugaring::AsyncGen => "async gen",
334 };
335 let source = match source {
336 hir::CoroutineSource::Block => "block",
337 hir::CoroutineSource::Closure => "closure",
338 hir::CoroutineSource::Fn => "function",
339 };
340 self.tcx.dcx().emit_err(BreakInsideCoroutine {
341 span,
342 coroutine_span,
343 name: &br_cx_kind.to_string(),
344 kind,
345 source,
346 });
347 }
348 UnlabeledBlock(block_span)
349 if br_cx_kind == BreakContextKind::Break && block_span.eq_ctxt(break_span) =>
350 {
351 let block = self.block_breaks.entry(block_span).or_insert_with(|| BlockInfo {
352 name: br_cx_kind.to_string(),
353 spans: vec![],
354 suggs: vec![],
355 });
356 block.spans.push(span);
357 block.suggs.push(break_span);
358 }
359 UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
360 self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
361 }
362 Normal | AnonConst | Fn | UnlabeledBlock(_) | UnlabeledIfBlock(_) | ConstBlock => {
363 self.tcx.dcx().emit_err(OutsideLoop {
364 spans: vec![span],
365 name: &br_cx_kind.to_string(),
366 is_break: br_cx_kind == BreakContextKind::Break,
367 suggestion: None,
368 });
369 }
370 }
371 }
372
373 fn require_label_in_labeled_block(
374 &self,
375 span: Span,
376 label: &Destination,
377 cf_type: &str,
378 ) -> bool {
379 if !span.is_desugaring(DesugaringKind::QuestionMark)
380 && self.cx_stack.last() == Some(&LabeledBlock)
381 && label.label.is_none()
382 {
383 self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
384 return true;
385 }
386 false
387 }
388
389 fn report_outside_loop_error(&self) {
390 for (s, block) in &self.block_breaks {
391 self.tcx.dcx().emit_err(OutsideLoop {
392 spans: block.spans.clone(),
393 name: &block.name,
394 is_break: true,
395 suggestion: Some(OutsideLoopSuggestion {
396 block_span: *s,
397 break_spans: block.suggs.clone(),
398 }),
399 });
400 }
401 }
402}