3 use rustc_errors
::{struct_span_err, Applicability}
;
5 use rustc_hir
::def_id
::LocalDefId
;
6 use rustc_hir
::intravisit
::{self, Visitor}
;
7 use rustc_hir
::{Destination, Movability, Node}
;
8 use rustc_middle
::hir
::map
::Map
;
9 use rustc_middle
::hir
::nested_filter
;
10 use rustc_middle
::ty
::query
::Providers
;
11 use rustc_middle
::ty
::TyCtxt
;
12 use rustc_session
::Session
;
13 use rustc_span
::hygiene
::DesugaringKind
;
16 #[derive(Clone, Copy, Debug, PartialEq)]
19 Loop(hir
::LoopSource
),
26 #[derive(Copy, Clone)]
27 struct CheckLoopVisitor
<'a
, 'hir
> {
33 fn check_mod_loops(tcx
: TyCtxt
<'_
>, module_def_id
: LocalDefId
) {
34 tcx
.hir().deep_visit_item_likes_in_module(
36 &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: tcx.hir(), cx: Normal }
,
40 pub(crate) fn provide(providers
: &mut Providers
) {
41 *providers
= Providers { check_mod_loops, ..*providers }
;
44 impl<'a
, 'hir
> Visitor
<'hir
> for CheckLoopVisitor
<'a
, 'hir
> {
45 type NestedFilter
= nested_filter
::OnlyBodies
;
47 fn nested_visit_map(&mut self) -> Self::Map
{
51 fn visit_anon_const(&mut self, c
: &'hir hir
::AnonConst
) {
52 self.with_context(AnonConst
, |v
| intravisit
::walk_anon_const(v
, c
));
55 fn visit_expr(&mut self, e
: &'hir hir
::Expr
<'hir
>) {
57 hir
::ExprKind
::Loop(ref b
, _
, source
, _
) => {
58 self.with_context(Loop(source
), |v
| v
.visit_block(&b
));
60 hir
::ExprKind
::Closure { ref fn_decl, body, fn_decl_span, movability, .. }
=> {
61 let cx
= if let Some(Movability
::Static
) = movability
{
62 AsyncClosure(fn_decl_span
)
66 self.visit_fn_decl(&fn_decl
);
67 self.with_context(cx
, |v
| v
.visit_nested_body(body
));
69 hir
::ExprKind
::Block(ref b
, Some(_label
)) => {
70 self.with_context(LabeledBlock
, |v
| v
.visit_block(&b
));
72 hir
::ExprKind
::Break(break_label
, ref opt_expr
) => {
73 if let Some(e
) = opt_expr
{
77 if self.require_label_in_labeled_block(e
.span
, &break_label
, "break") {
78 // If we emitted an error about an unlabeled break in a labeled
79 // block, we don't need any further checking for this break any more
83 let loop_id
= match break_label
.target_id
{
84 Ok(loop_id
) => Some(loop_id
),
85 Err(hir
::LoopIdError
::OutsideLoopScope
) => None
,
86 Err(hir
::LoopIdError
::UnlabeledCfInWhileCondition
) => {
87 self.emit_unlabled_cf_in_while_condition(e
.span
, "break");
90 Err(hir
::LoopIdError
::UnresolvedLabel
) => None
,
93 if let Some(Node
::Block(_
)) = loop_id
.and_then(|id
| self.hir_map
.find(id
)) {
97 if let Some(break_expr
) = opt_expr
{
98 let (head
, loop_label
, loop_kind
) = if let Some(loop_id
) = loop_id
{
99 match self.hir_map
.expect_expr(loop_id
).kind
{
100 hir
::ExprKind
::Loop(_
, label
, source
, sp
) => {
101 (Some(sp
), label
, Some(source
))
104 span_bug
!(e
.span
, "break label resolved to a non-loop: {:?}", r
)
111 None
| Some(hir
::LoopSource
::Loop
) => (),
113 let mut err
= struct_span_err
!(
117 "`break` with value from a `{}` loop",
122 "can only break with a value inside `loop` or breakable block",
124 if let Some(head
) = head
{
128 "you can't `break` with a value in a `{}` loop",
136 "use `break` on its own without a value inside this `{}` loop",
143 .map_or_else(String
::new
, |l
| format
!(" {}", l
.ident
))
145 Applicability
::MaybeIncorrect
,
147 if let (Some(label
), None
) = (loop_label
, break_label
.label
) {
148 match break_expr
.kind
{
149 hir
::ExprKind
::Path(hir
::QPath
::Resolved(
153 res
: hir
::def
::Res
::Err
,
156 )) if label
.ident
.to_string()
157 == format
!("'{}", segment
.ident
) =>
159 // This error is redundant, we will have already emitted a
160 // suggestion to use the label when `segment` wasn't found
161 // (hence the `Res::Err` check).
167 "alternatively, you might have meant to use the \
168 available loop label",
170 Applicability
::MaybeIncorrect
,
180 self.require_break_cx("break", e
.span
);
182 hir
::ExprKind
::Continue(destination
) => {
183 self.require_label_in_labeled_block(e
.span
, &destination
, "continue");
185 match destination
.target_id
{
187 if let Node
::Block(block
) = self.hir_map
.find(loop_id
).unwrap() {
192 "`continue` pointing to a labeled block"
194 .span_label(e
.span
, "labeled blocks cannot be `continue`'d")
195 .span_label(block
.span
, "labeled block the `continue` points to")
199 Err(hir
::LoopIdError
::UnlabeledCfInWhileCondition
) => {
200 self.emit_unlabled_cf_in_while_condition(e
.span
, "continue");
204 self.require_break_cx("continue", e
.span
)
206 _
=> intravisit
::walk_expr(self, e
),
211 impl<'a
, 'hir
> CheckLoopVisitor
<'a
, 'hir
> {
212 fn with_context
<F
>(&mut self, cx
: Context
, f
: F
)
214 F
: FnOnce(&mut CheckLoopVisitor
<'a
, 'hir
>),
216 let old_cx
= self.cx
;
222 fn require_break_cx(&self, name
: &str, span
: Span
) {
223 let err_inside_of
= |article
, ty
, closure_span
| {
224 struct_span_err
!(self.sess
, span
, E0267
, "`{}` inside of {} {}", name
, article
, ty
)
225 .span_label(span
, format
!("cannot `{}` inside of {} {}", name
, article
, ty
))
226 .span_label(closure_span
, &format
!("enclosing {}", ty
))
231 LabeledBlock
| Loop(_
) => {}
232 Closure(closure_span
) => err_inside_of("a", "closure", closure_span
),
233 AsyncClosure(closure_span
) => err_inside_of("an", "`async` block", closure_span
),
234 Normal
| AnonConst
=> {
235 struct_span_err
!(self.sess
, span
, E0268
, "`{}` outside of a loop", name
)
236 .span_label(span
, format
!("cannot `{}` outside of a loop", name
))
242 fn require_label_in_labeled_block(
248 if !span
.is_desugaring(DesugaringKind
::QuestionMark
) && self.cx
== LabeledBlock
{
249 if label
.label
.is_none() {
254 "unlabeled `{}` inside of a labeled block",
260 "`{}` statements that would diverge to or through \
261 a labeled block need to bear a label",
271 fn emit_unlabled_cf_in_while_condition(&mut self, span
: Span
, cf_type
: &str) {
276 "`break` or `continue` with no label in the condition of a `while` loop"
278 .span_label(span
, format
!("unlabeled `{}` in the condition of a `while` loop", cf_type
))