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, find_attr};
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::diagnostics::{
17 BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ConstContinueBadLabel,
18 ContinueLabeledBlock, OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition,
19 UnlabeledInLabeledBlock,
20};
21
22#[derive(#[automatically_derived]
impl ::core::clone::Clone for Context {
#[inline]
fn clone(&self) -> Context {
let _: ::core::clone::AssertParamIsClone<hir::LoopSource>;
let _: ::core::clone::AssertParamIsClone<Span>;
let _: ::core::clone::AssertParamIsClone<hir::CoroutineDesugaring>;
let _: ::core::clone::AssertParamIsClone<hir::CoroutineSource>;
let _: ::core::clone::AssertParamIsClone<Option<Span>>;
let _: ::core::clone::AssertParamIsClone<Destination>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Context { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for Context {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Context::Normal => ::core::fmt::Formatter::write_str(f, "Normal"),
Context::Fn => ::core::fmt::Formatter::write_str(f, "Fn"),
Context::Loop(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Loop",
&__self_0),
Context::Closure(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Closure", &__self_0),
Context::Coroutine {
coroutine_span: __self_0, kind: __self_1, source: __self_2 }
=>
::core::fmt::Formatter::debug_struct_field3_finish(f,
"Coroutine", "coroutine_span", __self_0, "kind", __self_1,
"source", &__self_2),
Context::UnlabeledBlock { label_span: __self_0, wrap_end: __self_1
} =>
::core::fmt::Formatter::debug_struct_field2_finish(f,
"UnlabeledBlock", "label_span", __self_0, "wrap_end",
&__self_1),
Context::UnlabeledIfBlock(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"UnlabeledIfBlock", &__self_0),
Context::LabeledBlock =>
::core::fmt::Formatter::write_str(f, "LabeledBlock"),
Context::AnonConst =>
::core::fmt::Formatter::write_str(f, "AnonConst"),
Context::ConstBlock =>
::core::fmt::Formatter::write_str(f, "ConstBlock"),
Context::LoopMatch { labeled_block: __self_0 } =>
::core::fmt::Formatter::debug_struct_field1_finish(f,
"LoopMatch", "labeled_block", &__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Context {
#[inline]
fn eq(&self, other: &Context) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(Context::Loop(__self_0), Context::Loop(__arg1_0)) =>
__self_0 == __arg1_0,
(Context::Closure(__self_0), Context::Closure(__arg1_0)) =>
__self_0 == __arg1_0,
(Context::Coroutine {
coroutine_span: __self_0, kind: __self_1, source: __self_2
}, Context::Coroutine {
coroutine_span: __arg1_0, kind: __arg1_1, source: __arg1_2
}) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1 &&
__self_2 == __arg1_2,
(Context::UnlabeledBlock {
label_span: __self_0, wrap_end: __self_1 },
Context::UnlabeledBlock {
label_span: __arg1_0, wrap_end: __arg1_1 }) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1,
(Context::UnlabeledIfBlock(__self_0),
Context::UnlabeledIfBlock(__arg1_0)) =>
__self_0 == __arg1_0,
(Context::LoopMatch { labeled_block: __self_0 },
Context::LoopMatch { labeled_block: __arg1_0 }) =>
__self_0 == __arg1_0,
_ => true,
}
}
}PartialEq)]
24enum Context {
25 Normal,
26 Fn,
27 Loop(hir::LoopSource),
28 Closure(Span),
29 Coroutine {
30 coroutine_span: Span,
31 kind: hir::CoroutineDesugaring,
32 source: hir::CoroutineSource,
33 },
34 UnlabeledBlock {
35 label_span: Span,
36 wrap_end: Option<Span>,
37 },
38 UnlabeledIfBlock(Span),
39 LabeledBlock,
40 AnonConst,
42 ConstBlock,
44 LoopMatch {
46 labeled_block: Destination,
48 },
49}
50
51#[derive(#[automatically_derived]
impl ::core::clone::Clone for BlockInfo {
#[inline]
fn clone(&self) -> BlockInfo {
BlockInfo {
name: ::core::clone::Clone::clone(&self.name),
spans: ::core::clone::Clone::clone(&self.spans),
suggs: ::core::clone::Clone::clone(&self.suggs),
wrap_end: ::core::clone::Clone::clone(&self.wrap_end),
}
}
}Clone)]
52struct BlockInfo {
53 name: String,
54 spans: Vec<Span>,
55 suggs: Vec<Span>,
56 wrap_end: Option<Span>,
57}
58
59#[derive(#[automatically_derived]
impl ::core::cmp::PartialEq for BreakContextKind {
#[inline]
fn eq(&self, other: &BreakContextKind) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq)]
60enum BreakContextKind {
61 Break,
62 Continue,
63}
64
65impl fmt::Display for BreakContextKind {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 BreakContextKind::Break => "break",
69 BreakContextKind::Continue => "continue",
70 }
71 .fmt(f)
72 }
73}
74
75#[derive(#[automatically_derived]
impl<'tcx> ::core::clone::Clone for CheckLoopVisitor<'tcx> {
#[inline]
fn clone(&self) -> CheckLoopVisitor<'tcx> {
CheckLoopVisitor {
tcx: ::core::clone::Clone::clone(&self.tcx),
cx_stack: ::core::clone::Clone::clone(&self.cx_stack),
block_breaks: ::core::clone::Clone::clone(&self.block_breaks),
}
}
}Clone)]
76struct CheckLoopVisitor<'tcx> {
77 tcx: TyCtxt<'tcx>,
78 cx_stack: Vec<Context>,
83 block_breaks: BTreeMap<Span, BlockInfo>,
84}
85
86pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir::Body<'tcx>) {
87 let mut check =
88 CheckLoopVisitor { tcx, cx_stack: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[Normal]))vec![Normal], block_breaks: Default::default() };
89 let cx = match tcx.def_kind(def_id) {
90 DefKind::AnonConst => AnonConst,
91 _ => Fn,
92 };
93 check.with_context(cx, |v| v.visit_body(body));
94 check.report_outside_loop_error();
95}
96
97impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
98 type NestedFilter = nested_filter::OnlyBodies;
99
100 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
101 self.tcx
102 }
103
104 fn visit_anon_const(&mut self, _: &'hir hir::AnonConst) {
105 }
107
108 fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) {
109 self.with_context(ConstBlock, |v| intravisit::walk_inline_const(v, c));
110 }
111
112 fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
113 match e.kind {
114 hir::ExprKind::If(cond, then, else_opt) => {
115 self.visit_expr(cond);
116
117 let get_block = |ck_loop: &CheckLoopVisitor<'hir>,
118 expr: &hir::Expr<'hir>|
119 -> Option<&hir::Block<'hir>> {
120 if let hir::ExprKind::Block(b, None) = expr.kind
121 && #[allow(non_exhaustive_omitted_patterns)] match ck_loop.cx_stack.last() {
Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock { .. }) |
Some(&UnlabeledIfBlock(_)) => true,
_ => false,
}matches!(
122 ck_loop.cx_stack.last(),
123 Some(&Normal)
124 | Some(&AnonConst)
125 | Some(&UnlabeledBlock { .. })
126 | Some(&UnlabeledIfBlock(_))
127 )
128 {
129 Some(b)
130 } else {
131 None
132 }
133 };
134
135 if let Some(b) = get_block(self, then) {
136 self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
137 v.visit_block(b)
138 });
139 } else {
140 self.visit_expr(then);
141 }
142
143 if let Some(else_expr) = else_opt {
144 if let Some(b) = get_block(self, else_expr) {
145 self.with_context(UnlabeledIfBlock(b.span.shrink_to_lo()), |v| {
146 v.visit_block(b)
147 });
148 } else {
149 self.visit_expr(else_expr);
150 }
151 }
152 }
153 hir::ExprKind::Loop(b, _, source, _) => {
154 let cx = match self.is_loop_match(e, b) {
155 Some(labeled_block) => LoopMatch { labeled_block },
156 None => Loop(source),
157 };
158
159 self.with_context(cx, |v| v.visit_block(b));
160 }
161 hir::ExprKind::Closure(&hir::Closure { fn_decl, body, fn_decl_span, kind, .. }) => {
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(b, Some(_label)) => {
172 self.with_context(LabeledBlock, |v| v.visit_block(b));
173 }
174 hir::ExprKind::Block(b, None)
175 if #[allow(non_exhaustive_omitted_patterns)] match self.cx_stack.last() {
Some(&Fn) | Some(&ConstBlock) => true,
_ => false,
}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 b @ hir::Block { rules: hir::BlockCheckMode::DefaultBlock, .. },
181 None,
182 ) if #[allow(non_exhaustive_omitted_patterns)] match self.cx_stack.last() {
Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock { .. }) => true,
_ => false,
}matches!(
183 self.cx_stack.last(),
184 Some(&Normal) | Some(&AnonConst) | Some(&UnlabeledBlock { .. })
185 ) =>
186 {
187 let wrap_end = b.targeted_by_break.then(|| b.span.shrink_to_hi());
190 self.with_context(
191 UnlabeledBlock { label_span: b.span.shrink_to_lo(), wrap_end },
192 |v| v.visit_block(b),
193 );
194 }
195 hir::ExprKind::Break(break_destination, 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_destination, "break") {
201 return;
204 }
205
206 let loop_id = match break_destination.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 {
{
'done:
{
for i in
::rustc_attr_ir::HasAttrs::get_attrs(e.hir_id, &self.tcx) {
#[allow(unused_imports)]
use ::rustc_attr_ir::AttributeKind::*;
let i: &::rustc_attr_ir::Attribute = i;
match i {
::rustc_attr_ir::Attribute::Parsed(ConstContinue(_)) => {
break 'done Some(());
}
::rustc_attr_ir::Attribute::Unparsed(..) =>
{}
#[deny(unreachable_patterns)]
_ => {}
}
}
None
}
}
}.is_some()find_attr!(self.tcx, e.hir_id, ConstContinue(_)) {
221 let Some(label) = break_destination.label else {
222 let span = e.span;
223 self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
224 };
225
226 let is_target_label = |cx: &Context| match cx {
227 Context::LoopMatch { labeled_block } => {
228 if !labeled_block.target_id.is_ok() {
::core::panicking::panic("assertion failed: labeled_block.target_id.is_ok()")
};assert!(labeled_block.target_id.is_ok()); break_destination.target_id == labeled_block.target_id
233 }
234 _ => false,
235 };
236
237 if !self.cx_stack.iter().rev().any(is_target_label) {
238 let span = label.ident.span;
239 self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
240 }
241 }
242
243 if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) {
244 return;
245 }
246
247 if let Some(break_expr) = opt_expr {
248 let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
249 match self.tcx.hir_expect_expr(loop_id).kind {
250 hir::ExprKind::Loop(_, label, source, sp) => {
251 (Some(sp), label, Some(source))
252 }
253 ref r => {
254 ::rustc_middle::util::bug::span_bug_fmt(e.span,
format_args!("break label resolved to a non-loop: {0:?}", r))span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
255 }
256 }
257 } else {
258 (None, None, None)
259 };
260 match loop_kind {
261 None | Some(hir::LoopSource::Loop) => (),
262 Some(kind) => {
263 let suggestion = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("break{0}",
break_destination.label.map_or_else(String::new,
|l|
::alloc::__export::must_use({
::alloc::fmt::format(format_args!(" {0}", l.ident))
}))))
})format!(
264 "break{}",
265 break_destination
266 .label
267 .map_or_else(String::new, |l| format!(" {}", l.ident))
268 );
269 self.tcx.dcx().emit_err(BreakNonLoop {
270 span: e.span,
271 head,
272 kind: kind.name(),
273 suggestion,
274 loop_label,
275 break_label: break_destination.label,
276 break_expr_kind: &break_expr.kind,
277 break_expr_span: break_expr.span,
278 });
279 }
280 }
281 }
282
283 let sp_lo = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(e.span)
284 && let Some(break_pos) = snippet.find("break")
285 {
286 e.span.with_lo(e.span.lo() + BytePos((break_pos + "break".len()) as u32))
287 } else {
288 e.span.with_lo(e.span.lo() + BytePos("break".len() as u32))
289 };
290 let label_sp = match break_destination.label {
291 Some(label) => sp_lo.with_hi(label.ident.span.hi()),
292 None => sp_lo.shrink_to_lo(),
293 };
294 self.require_break_cx(
295 BreakContextKind::Break,
296 e.span,
297 label_sp,
298 self.cx_stack.len() - 1,
299 );
300 }
301 hir::ExprKind::Continue(destination) => {
302 self.require_label_in_labeled_block(e.span, &destination, "continue");
303
304 match destination.target_id {
305 Ok(loop_id) => {
306 if let Node::Block(block) = self.tcx.hir_node(loop_id) {
307 self.tcx.dcx().emit_err(ContinueLabeledBlock {
308 span: e.span,
309 block_span: block.span,
310 });
311 }
312 }
313 Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
314 self.tcx.dcx().emit_err(UnlabeledCfInWhileCondition {
315 span: e.span,
316 cf_type: "continue",
317 });
318 }
319 Err(_) => {}
320 }
321 self.require_break_cx(
322 BreakContextKind::Continue,
323 e.span,
324 e.span,
325 self.cx_stack.len() - 1,
326 )
327 }
328 _ => intravisit::walk_expr(self, e),
329 }
330 }
331}
332
333impl<'hir> CheckLoopVisitor<'hir> {
334 fn with_context<F>(&mut self, cx: Context, f: F)
335 where
336 F: FnOnce(&mut CheckLoopVisitor<'hir>),
337 {
338 self.cx_stack.push(cx);
339 f(self);
340 self.cx_stack.pop();
341 }
342
343 fn require_break_cx(
344 &mut self,
345 br_cx_kind: BreakContextKind,
346 span: Span,
347 break_span: Span,
348 cx_pos: usize,
349 ) {
350 match self.cx_stack[cx_pos] {
351 LabeledBlock | Loop(_) | LoopMatch { .. } => {}
352 Closure(closure_span) => {
353 self.tcx.dcx().emit_err(BreakInsideClosure {
354 span,
355 closure_span,
356 name: &br_cx_kind.to_string(),
357 });
358 }
359 Coroutine { coroutine_span, kind, source } => {
360 let kind = match kind {
361 hir::CoroutineDesugaring::Async => "async",
362 hir::CoroutineDesugaring::Gen => "gen",
363 hir::CoroutineDesugaring::AsyncGen => "async gen",
364 };
365 let source = match source {
366 hir::CoroutineSource::Block => "block",
367 hir::CoroutineSource::Closure => "closure",
368 hir::CoroutineSource::Fn => "function",
369 };
370 self.tcx.dcx().emit_err(BreakInsideCoroutine {
371 span,
372 coroutine_span,
373 name: &br_cx_kind.to_string(),
374 kind,
375 source,
376 });
377 }
378 UnlabeledBlock { label_span, wrap_end }
379 if br_cx_kind == BreakContextKind::Break && label_span.eq_ctxt(break_span) =>
380 {
381 let block = self.block_breaks.entry(label_span).or_insert_with(|| BlockInfo {
382 name: br_cx_kind.to_string(),
383 spans: ::alloc::vec::Vec::new()vec![],
384 suggs: ::alloc::vec::Vec::new()vec![],
385 wrap_end,
386 });
387 block.spans.push(span);
388 block.suggs.push(break_span);
389 }
390 UnlabeledIfBlock(_) if br_cx_kind == BreakContextKind::Break => {
391 self.require_break_cx(br_cx_kind, span, break_span, cx_pos - 1);
392 }
393 Normal | AnonConst | Fn | UnlabeledBlock { .. } | UnlabeledIfBlock(_) | ConstBlock => {
394 self.tcx.dcx().emit_err(OutsideLoop {
395 spans: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[span]))vec![span],
396 name: &br_cx_kind.to_string(),
397 is_break: br_cx_kind == BreakContextKind::Break,
398 suggestion: None,
399 });
400 }
401 }
402 }
403
404 fn require_label_in_labeled_block(
405 &self,
406 span: Span,
407 label: &Destination,
408 cf_type: &str,
409 ) -> bool {
410 if !span.is_desugaring(DesugaringKind::QuestionMark)
411 && self.cx_stack.last() == Some(&LabeledBlock)
412 && label.label.is_none()
413 {
414 self.tcx.dcx().emit_err(UnlabeledInLabeledBlock { span, cf_type });
415 return true;
416 }
417 false
418 }
419
420 fn report_outside_loop_error(&self) {
421 for (s, block) in &self.block_breaks {
422 self.tcx.dcx().emit_err(OutsideLoop {
423 spans: block.spans.clone(),
424 name: &block.name,
425 is_break: true,
426 suggestion: Some(OutsideLoopSuggestion {
427 block_span: *s,
428 break_spans: block.suggs.clone(),
429 block_prefix: if block.wrap_end.is_some() { "{ 'block: " } else { "'block: " },
430 wrap_end: block.wrap_end,
431 }),
432 });
433 }
434 }
435
436 fn is_loop_match(
438 &self,
439 e: &'hir hir::Expr<'hir>,
440 body: &'hir hir::Block<'hir>,
441 ) -> Option<Destination> {
442 if !{
{
'done:
{
for i in
::rustc_attr_ir::HasAttrs::get_attrs(e.hir_id, &self.tcx) {
#[allow(unused_imports)]
use ::rustc_attr_ir::AttributeKind::*;
let i: &::rustc_attr_ir::Attribute = i;
match i {
::rustc_attr_ir::Attribute::Parsed(LoopMatch(_)) => {
break 'done Some(());
}
::rustc_attr_ir::Attribute::Unparsed(..) =>
{}
#[deny(unreachable_patterns)]
_ => {}
}
}
None
}
}
}.is_some()find_attr!(self.tcx, e.hir_id, LoopMatch(_)) {
443 return None;
444 }
445
446 let loop_body_expr = match body.stmts {
450 [] => body.expr?,
451 [single] if body.expr.is_none() => match single.kind {
452 hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr,
453 _ => return None,
454 },
455 [..] => return None,
456 };
457
458 let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };
459
460 let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };
461
462 Some(Destination { label, target_id: Ok(block.hir_id) })
463 }
464}