]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_passes/src/loops.rs
Auto merge of #100218 - nicholasbishop:bishop-update-cb, r=Mark-Simulacrum
[rust.git] / compiler / rustc_passes / src / loops.rs
1 use Context::*;
2
3 use rustc_errors::{struct_span_err, Applicability};
4 use rustc_hir as hir;
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;
14 use rustc_span::Span;
15
16 #[derive(Clone, Copy, Debug, PartialEq)]
17 enum Context {
18     Normal,
19     Loop(hir::LoopSource),
20     Closure(Span),
21     AsyncClosure(Span),
22     LabeledBlock,
23     AnonConst,
24 }
25
26 #[derive(Copy, Clone)]
27 struct CheckLoopVisitor<'a, 'hir> {
28     sess: &'a Session,
29     hir_map: Map<'hir>,
30     cx: Context,
31 }
32
33 fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
34     tcx.hir().visit_item_likes_in_module(
35         module_def_id,
36         &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: tcx.hir(), cx: Normal },
37     );
38 }
39
40 pub(crate) fn provide(providers: &mut Providers) {
41     *providers = Providers { check_mod_loops, ..*providers };
42 }
43
44 impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
45     type NestedFilter = nested_filter::OnlyBodies;
46
47     fn nested_visit_map(&mut self) -> Self::Map {
48         self.hir_map
49     }
50
51     fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
52         self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
53     }
54
55     fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
56         match e.kind {
57             hir::ExprKind::Loop(ref b, _, source, _) => {
58                 self.with_context(Loop(source), |v| v.visit_block(&b));
59             }
60             hir::ExprKind::Closure(&hir::Closure {
61                 ref fn_decl,
62                 body,
63                 fn_decl_span,
64                 movability,
65                 ..
66             }) => {
67                 let cx = if let Some(Movability::Static) = movability {
68                     AsyncClosure(fn_decl_span)
69                 } else {
70                     Closure(fn_decl_span)
71                 };
72                 self.visit_fn_decl(&fn_decl);
73                 self.with_context(cx, |v| v.visit_nested_body(body));
74             }
75             hir::ExprKind::Block(ref b, Some(_label)) => {
76                 self.with_context(LabeledBlock, |v| v.visit_block(&b));
77             }
78             hir::ExprKind::Break(break_label, ref opt_expr) => {
79                 if let Some(e) = opt_expr {
80                     self.visit_expr(e);
81                 }
82
83                 if self.require_label_in_labeled_block(e.span, &break_label, "break") {
84                     // If we emitted an error about an unlabeled break in a labeled
85                     // block, we don't need any further checking for this break any more
86                     return;
87                 }
88
89                 let loop_id = match break_label.target_id {
90                     Ok(loop_id) => Some(loop_id),
91                     Err(hir::LoopIdError::OutsideLoopScope) => None,
92                     Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
93                         self.emit_unlabled_cf_in_while_condition(e.span, "break");
94                         None
95                     }
96                     Err(hir::LoopIdError::UnresolvedLabel) => None,
97                 };
98
99                 if let Some(Node::Block(_)) = loop_id.and_then(|id| self.hir_map.find(id)) {
100                     return;
101                 }
102
103                 if let Some(break_expr) = opt_expr {
104                     let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
105                         match self.hir_map.expect_expr(loop_id).kind {
106                             hir::ExprKind::Loop(_, label, source, sp) => {
107                                 (Some(sp), label, Some(source))
108                             }
109                             ref r => {
110                                 span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
111                             }
112                         }
113                     } else {
114                         (None, None, None)
115                     };
116                     match loop_kind {
117                         None | Some(hir::LoopSource::Loop) => (),
118                         Some(kind) => {
119                             let mut err = struct_span_err!(
120                                 self.sess,
121                                 e.span,
122                                 E0571,
123                                 "`break` with value from a `{}` loop",
124                                 kind.name()
125                             );
126                             err.span_label(
127                                 e.span,
128                                 "can only break with a value inside `loop` or breakable block",
129                             );
130                             if let Some(head) = head {
131                                 err.span_label(
132                                     head,
133                                     &format!(
134                                         "you can't `break` with a value in a `{}` loop",
135                                         kind.name()
136                                     ),
137                                 );
138                             }
139                             err.span_suggestion(
140                                 e.span,
141                                 &format!(
142                                     "use `break` on its own without a value inside this `{}` loop",
143                                     kind.name(),
144                                 ),
145                                 format!(
146                                     "break{}",
147                                     break_label
148                                         .label
149                                         .map_or_else(String::new, |l| format!(" {}", l.ident))
150                                 ),
151                                 Applicability::MaybeIncorrect,
152                             );
153                             if let (Some(label), None) = (loop_label, break_label.label) {
154                                 match break_expr.kind {
155                                     hir::ExprKind::Path(hir::QPath::Resolved(
156                                         None,
157                                         hir::Path {
158                                             segments: [segment],
159                                             res: hir::def::Res::Err,
160                                             ..
161                                         },
162                                     )) if label.ident.to_string()
163                                         == format!("'{}", segment.ident) =>
164                                     {
165                                         // This error is redundant, we will have already emitted a
166                                         // suggestion to use the label when `segment` wasn't found
167                                         // (hence the `Res::Err` check).
168                                         err.delay_as_bug();
169                                     }
170                                     _ => {
171                                         err.span_suggestion(
172                                             break_expr.span,
173                                             "alternatively, you might have meant to use the \
174                                              available loop label",
175                                             label.ident,
176                                             Applicability::MaybeIncorrect,
177                                         );
178                                     }
179                                 }
180                             }
181                             err.emit();
182                         }
183                     }
184                 }
185
186                 self.require_break_cx("break", e.span);
187             }
188             hir::ExprKind::Continue(destination) => {
189                 self.require_label_in_labeled_block(e.span, &destination, "continue");
190
191                 match destination.target_id {
192                     Ok(loop_id) => {
193                         if let Node::Block(block) = self.hir_map.find(loop_id).unwrap() {
194                             struct_span_err!(
195                                 self.sess,
196                                 e.span,
197                                 E0696,
198                                 "`continue` pointing to a labeled block"
199                             )
200                             .span_label(e.span, "labeled blocks cannot be `continue`'d")
201                             .span_label(block.span, "labeled block the `continue` points to")
202                             .emit();
203                         }
204                     }
205                     Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
206                         self.emit_unlabled_cf_in_while_condition(e.span, "continue");
207                     }
208                     Err(_) => {}
209                 }
210                 self.require_break_cx("continue", e.span)
211             }
212             _ => intravisit::walk_expr(self, e),
213         }
214     }
215 }
216
217 impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
218     fn with_context<F>(&mut self, cx: Context, f: F)
219     where
220         F: FnOnce(&mut CheckLoopVisitor<'a, 'hir>),
221     {
222         let old_cx = self.cx;
223         self.cx = cx;
224         f(self);
225         self.cx = old_cx;
226     }
227
228     fn require_break_cx(&self, name: &str, span: Span) {
229         let err_inside_of = |article, ty, closure_span| {
230             struct_span_err!(self.sess, span, E0267, "`{}` inside of {} {}", name, article, ty)
231                 .span_label(span, format!("cannot `{}` inside of {} {}", name, article, ty))
232                 .span_label(closure_span, &format!("enclosing {}", ty))
233                 .emit();
234         };
235
236         match self.cx {
237             LabeledBlock | Loop(_) => {}
238             Closure(closure_span) => err_inside_of("a", "closure", closure_span),
239             AsyncClosure(closure_span) => err_inside_of("an", "`async` block", closure_span),
240             Normal | AnonConst => {
241                 struct_span_err!(self.sess, span, E0268, "`{}` outside of a loop", name)
242                     .span_label(span, format!("cannot `{}` outside of a loop", name))
243                     .emit();
244             }
245         }
246     }
247
248     fn require_label_in_labeled_block(
249         &mut self,
250         span: Span,
251         label: &Destination,
252         cf_type: &str,
253     ) -> bool {
254         if !span.is_desugaring(DesugaringKind::QuestionMark) && self.cx == LabeledBlock {
255             if label.label.is_none() {
256                 struct_span_err!(
257                     self.sess,
258                     span,
259                     E0695,
260                     "unlabeled `{}` inside of a labeled block",
261                     cf_type
262                 )
263                 .span_label(
264                     span,
265                     format!(
266                         "`{}` statements that would diverge to or through \
267                                 a labeled block need to bear a label",
268                         cf_type
269                     ),
270                 )
271                 .emit();
272                 return true;
273             }
274         }
275         false
276     }
277     fn emit_unlabled_cf_in_while_condition(&mut self, span: Span, cf_type: &str) {
278         struct_span_err!(
279             self.sess,
280             span,
281             E0590,
282             "`break` or `continue` with no label in the condition of a `while` loop"
283         )
284         .span_label(span, format!("unlabeled `{}` in the condition of a `while` loop", cf_type))
285         .emit();
286     }
287 }