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