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