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