]> git.lizzy.rs Git - rust.git/blob - src/librustc_passes/loops.rs
Rollup merge of #68292 - matthiaskrgr:clone_on_copy, r=eddyb
[rust.git] / src / librustc_passes / loops.rs
1 use Context::*;
2
3 use rustc::session::Session;
4
5 use rustc::hir::map::Map;
6 use rustc::ty::query::Providers;
7 use rustc::ty::TyCtxt;
8 use rustc_errors::{struct_span_err, Applicability};
9 use rustc_hir as hir;
10 use rustc_hir::def_id::DefId;
11 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
12 use rustc_hir::{Destination, Movability, Node};
13 use rustc_span::Span;
14
15 use rustc_error_codes::*;
16
17 #[derive(Clone, Copy, Debug, PartialEq)]
18 enum Context {
19     Normal,
20     Loop(hir::LoopSource),
21     Closure(Span),
22     AsyncClosure(Span),
23     LabeledBlock,
24     AnonConst,
25 }
26
27 #[derive(Copy, Clone)]
28 struct CheckLoopVisitor<'a, 'hir> {
29     sess: &'a Session,
30     hir_map: &'a Map<'hir>,
31     cx: Context,
32 }
33
34 fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: DefId) {
35     tcx.hir().visit_item_likes_in_module(
36         module_def_id,
37         &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: &tcx.hir(), cx: Normal }
38             .as_deep_visitor(),
39     );
40 }
41
42 pub(crate) fn provide(providers: &mut Providers<'_>) {
43     *providers = Providers { check_mod_loops, ..*providers };
44 }
45
46 impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
47     type Map = Map<'hir>;
48
49     fn nested_visit_map(&mut self) -> NestedVisitorMap<'_, Self::Map> {
50         NestedVisitorMap::OnlyBodies(&self.hir_map)
51     }
52
53     fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
54         self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
55     }
56
57     fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
58         match e.kind {
59             hir::ExprKind::Loop(ref b, _, source) => {
60                 self.with_context(Loop(source), |v| v.visit_block(&b));
61             }
62             hir::ExprKind::Closure(_, ref function_decl, b, span, movability) => {
63                 let cx = if let Some(Movability::Static) = movability {
64                     AsyncClosure(span)
65                 } else {
66                     Closure(span)
67                 };
68                 self.visit_fn_decl(&function_decl);
69                 self.with_context(cx, |v| v.visit_nested_body(b));
70             }
71             hir::ExprKind::Block(ref b, Some(_label)) => {
72                 self.with_context(LabeledBlock, |v| v.visit_block(&b));
73             }
74             hir::ExprKind::Break(label, ref opt_expr) => {
75                 opt_expr.as_ref().map(|e| self.visit_expr(e));
76
77                 if self.require_label_in_labeled_block(e.span, &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
80                     return;
81                 }
82
83                 let loop_id = match label.target_id.into() {
84                     Ok(loop_id) => loop_id,
85                     Err(hir::LoopIdError::OutsideLoopScope) => hir::DUMMY_HIR_ID,
86                     Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
87                         self.emit_unlabled_cf_in_while_condition(e.span, "break");
88                         hir::DUMMY_HIR_ID
89                     }
90                     Err(hir::LoopIdError::UnresolvedLabel) => hir::DUMMY_HIR_ID,
91                 };
92
93                 if loop_id != hir::DUMMY_HIR_ID {
94                     if let Node::Block(_) = self.hir_map.find(loop_id).unwrap() {
95                         return;
96                     }
97                 }
98
99                 if opt_expr.is_some() {
100                     let loop_kind = if loop_id == hir::DUMMY_HIR_ID {
101                         None
102                     } else {
103                         Some(match self.hir_map.expect_expr(loop_id).kind {
104                             hir::ExprKind::Loop(_, _, source) => source,
105                             ref r => {
106                                 span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
107                             }
108                         })
109                     };
110                     match loop_kind {
111                         None | Some(hir::LoopSource::Loop) => (),
112                         Some(kind) => {
113                             struct_span_err!(
114                                 self.sess,
115                                 e.span,
116                                 E0571,
117                                 "`break` with value from a `{}` loop",
118                                 kind.name()
119                             )
120                             .span_label(
121                                 e.span,
122                                 "can only break with a value inside \
123                                             `loop` or breakable block",
124                             )
125                             .span_suggestion(
126                                 e.span,
127                                 &format!(
128                                     "instead, use `break` on its own \
129                                         without a value inside this `{}` loop",
130                                     kind.name()
131                                 ),
132                                 "break".to_string(),
133                                 Applicability::MaybeIncorrect,
134                             )
135                             .emit();
136                         }
137                     }
138                 }
139
140                 self.require_break_cx("break", e.span);
141             }
142             hir::ExprKind::Continue(destination) => {
143                 self.require_label_in_labeled_block(e.span, &destination, "continue");
144
145                 match destination.target_id {
146                     Ok(loop_id) => {
147                         if let Node::Block(block) = self.hir_map.find(loop_id).unwrap() {
148                             struct_span_err!(
149                                 self.sess,
150                                 e.span,
151                                 E0696,
152                                 "`continue` pointing to a labeled block"
153                             )
154                             .span_label(e.span, "labeled blocks cannot be `continue`'d")
155                             .span_label(block.span, "labeled block the `continue` points to")
156                             .emit();
157                         }
158                     }
159                     Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
160                         self.emit_unlabled_cf_in_while_condition(e.span, "continue");
161                     }
162                     Err(_) => {}
163                 }
164                 self.require_break_cx("continue", e.span)
165             }
166             _ => intravisit::walk_expr(self, e),
167         }
168     }
169 }
170
171 impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
172     fn with_context<F>(&mut self, cx: Context, f: F)
173     where
174         F: FnOnce(&mut CheckLoopVisitor<'a, 'hir>),
175     {
176         let old_cx = self.cx;
177         self.cx = cx;
178         f(self);
179         self.cx = old_cx;
180     }
181
182     fn require_break_cx(&self, name: &str, span: Span) {
183         let err_inside_of = |article, ty, closure_span| {
184             struct_span_err!(self.sess, span, E0267, "`{}` inside of {} {}", name, article, ty)
185                 .span_label(span, format!("cannot `{}` inside of {} {}", name, article, ty))
186                 .span_label(closure_span, &format!("enclosing {}", ty))
187                 .emit();
188         };
189
190         match self.cx {
191             LabeledBlock | Loop(_) => {}
192             Closure(closure_span) => err_inside_of("a", "closure", closure_span),
193             AsyncClosure(closure_span) => err_inside_of("an", "`async` block", closure_span),
194             Normal | AnonConst => {
195                 struct_span_err!(self.sess, span, E0268, "`{}` outside of a loop", name)
196                     .span_label(span, format!("cannot `{}` outside of a loop", name))
197                     .emit();
198             }
199         }
200     }
201
202     fn require_label_in_labeled_block(
203         &mut self,
204         span: Span,
205         label: &Destination,
206         cf_type: &str,
207     ) -> bool {
208         if self.cx == LabeledBlock {
209             if label.label.is_none() {
210                 struct_span_err!(
211                     self.sess,
212                     span,
213                     E0695,
214                     "unlabeled `{}` inside of a labeled block",
215                     cf_type
216                 )
217                 .span_label(
218                     span,
219                     format!(
220                         "`{}` statements that would diverge to or through \
221                                 a labeled block need to bear a label",
222                         cf_type
223                     ),
224                 )
225                 .emit();
226                 return true;
227             }
228         }
229         return false;
230     }
231     fn emit_unlabled_cf_in_while_condition(&mut self, span: Span, cf_type: &str) {
232         struct_span_err!(
233             self.sess,
234             span,
235             E0590,
236             "`break` or `continue` with no label in the condition of a `while` loop"
237         )
238         .span_label(span, format!("unlabeled `{}` in the condition of a `while` loop", cf_type))
239         .emit();
240     }
241 }