]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_passes/src/loops.rs
Auto merge of #107667 - cjgillot:no-on-hit, r=lcnr,Zoxc
[rust.git] / compiler / rustc_passes / src / loops.rs
1 use Context::*;
2
3 use rustc_hir as hir;
4 use rustc_hir::def_id::LocalDefId;
5 use rustc_hir::intravisit::{self, Visitor};
6 use rustc_hir::{Destination, Movability, Node};
7 use rustc_middle::hir::map::Map;
8 use rustc_middle::hir::nested_filter;
9 use rustc_middle::ty::query::Providers;
10 use rustc_middle::ty::TyCtxt;
11 use rustc_session::Session;
12 use rustc_span::hygiene::DesugaringKind;
13 use rustc_span::Span;
14
15 use crate::errors::{
16     BreakInsideAsyncBlock, BreakInsideClosure, BreakNonLoop, ContinueLabeledBlock, OutsideLoop,
17     UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock,
18 };
19
20 #[derive(Clone, Copy, Debug, PartialEq)]
21 enum Context {
22     Normal,
23     Loop(hir::LoopSource),
24     Closure(Span),
25     AsyncClosure(Span),
26     LabeledBlock,
27     AnonConst,
28 }
29
30 #[derive(Copy, Clone)]
31 struct CheckLoopVisitor<'a, 'hir> {
32     sess: &'a Session,
33     hir_map: Map<'hir>,
34     cx: Context,
35 }
36
37 fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
38     tcx.hir().visit_item_likes_in_module(
39         module_def_id,
40         &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: tcx.hir(), cx: Normal },
41     );
42 }
43
44 pub(crate) fn provide(providers: &mut Providers) {
45     *providers = Providers { check_mod_loops, ..*providers };
46 }
47
48 impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
49     type NestedFilter = nested_filter::OnlyBodies;
50
51     fn nested_visit_map(&mut self) -> Self::Map {
52         self.hir_map
53     }
54
55     fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
56         self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
57     }
58
59     fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
60         match e.kind {
61             hir::ExprKind::Loop(ref b, _, source, _) => {
62                 self.with_context(Loop(source), |v| v.visit_block(&b));
63             }
64             hir::ExprKind::Closure(&hir::Closure {
65                 ref fn_decl,
66                 body,
67                 fn_decl_span,
68                 movability,
69                 ..
70             }) => {
71                 let cx = if let Some(Movability::Static) = movability {
72                     AsyncClosure(fn_decl_span)
73                 } else {
74                     Closure(fn_decl_span)
75                 };
76                 self.visit_fn_decl(&fn_decl);
77                 self.with_context(cx, |v| v.visit_nested_body(body));
78             }
79             hir::ExprKind::Block(ref b, Some(_label)) => {
80                 self.with_context(LabeledBlock, |v| v.visit_block(&b));
81             }
82             hir::ExprKind::Break(break_label, ref opt_expr) => {
83                 if let Some(e) = opt_expr {
84                     self.visit_expr(e);
85                 }
86
87                 if self.require_label_in_labeled_block(e.span, &break_label, "break") {
88                     // If we emitted an error about an unlabeled break in a labeled
89                     // block, we don't need any further checking for this break any more
90                     return;
91                 }
92
93                 let loop_id = match break_label.target_id {
94                     Ok(loop_id) => Some(loop_id),
95                     Err(hir::LoopIdError::OutsideLoopScope) => None,
96                     Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
97                         self.sess.emit_err(UnlabeledCfInWhileCondition {
98                             span: e.span,
99                             cf_type: "break",
100                         });
101                         None
102                     }
103                     Err(hir::LoopIdError::UnresolvedLabel) => None,
104                 };
105
106                 if let Some(Node::Block(_)) = loop_id.and_then(|id| self.hir_map.find(id)) {
107                     return;
108                 }
109
110                 if let Some(break_expr) = opt_expr {
111                     let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
112                         match self.hir_map.expect_expr(loop_id).kind {
113                             hir::ExprKind::Loop(_, label, source, sp) => {
114                                 (Some(sp), label, Some(source))
115                             }
116                             ref r => {
117                                 span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
118                             }
119                         }
120                     } else {
121                         (None, None, None)
122                     };
123                     match loop_kind {
124                         None | Some(hir::LoopSource::Loop) => (),
125                         Some(kind) => {
126                             let suggestion = format!(
127                                 "break{}",
128                                 break_label
129                                     .label
130                                     .map_or_else(String::new, |l| format!(" {}", l.ident))
131                             );
132                             self.sess.emit_err(BreakNonLoop {
133                                 span: e.span,
134                                 head,
135                                 kind: kind.name(),
136                                 suggestion,
137                                 loop_label,
138                                 break_label: break_label.label,
139                                 break_expr_kind: &break_expr.kind,
140                                 break_expr_span: break_expr.span,
141                             });
142                         }
143                     }
144                 }
145
146                 self.require_break_cx("break", e.span);
147             }
148             hir::ExprKind::Continue(destination) => {
149                 self.require_label_in_labeled_block(e.span, &destination, "continue");
150
151                 match destination.target_id {
152                     Ok(loop_id) => {
153                         if let Node::Block(block) = self.hir_map.find(loop_id).unwrap() {
154                             self.sess.emit_err(ContinueLabeledBlock {
155                                 span: e.span,
156                                 block_span: block.span,
157                             });
158                         }
159                     }
160                     Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
161                         self.sess.emit_err(UnlabeledCfInWhileCondition {
162                             span: e.span,
163                             cf_type: "continue",
164                         });
165                     }
166                     Err(_) => {}
167                 }
168                 self.require_break_cx("continue", e.span)
169             }
170             _ => intravisit::walk_expr(self, e),
171         }
172     }
173 }
174
175 impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
176     fn with_context<F>(&mut self, cx: Context, f: F)
177     where
178         F: FnOnce(&mut CheckLoopVisitor<'a, 'hir>),
179     {
180         let old_cx = self.cx;
181         self.cx = cx;
182         f(self);
183         self.cx = old_cx;
184     }
185
186     fn require_break_cx(&self, name: &str, span: Span) {
187         match self.cx {
188             LabeledBlock | Loop(_) => {}
189             Closure(closure_span) => {
190                 self.sess.emit_err(BreakInsideClosure { span, closure_span, name });
191             }
192             AsyncClosure(closure_span) => {
193                 self.sess.emit_err(BreakInsideAsyncBlock { span, closure_span, name });
194             }
195             Normal | AnonConst => {
196                 self.sess.emit_err(OutsideLoop { span, name, is_break: name == "break" });
197             }
198         }
199     }
200
201     fn require_label_in_labeled_block(
202         &mut self,
203         span: Span,
204         label: &Destination,
205         cf_type: &str,
206     ) -> bool {
207         if !span.is_desugaring(DesugaringKind::QuestionMark)
208             && self.cx == LabeledBlock
209             && label.label.is_none()
210         {
211             self.sess.emit_err(UnlabeledInLabeledBlock { span, cf_type });
212             return true;
213         }
214         false
215     }
216 }