]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/mutability_errors.rs
Auto merge of #57609 - matthewjasper:more-restrictive-match, r=pnkfelix
[rust.git] / src / librustc_mir / borrow_check / mutability_errors.rs
1 use rustc::hir;
2 use rustc::hir::Node;
3 use rustc::mir::{self, BindingForm, Constant, ClearCrossCrate, Local, Location, Mir};
4 use rustc::mir::{Mutability, Operand, Place, Projection, ProjectionElem, Static, Terminator};
5 use rustc::mir::TerminatorKind;
6 use rustc::ty::{self, Const, DefIdTree, TyS, TyKind, TyCtxt};
7 use rustc_data_structures::indexed_vec::Idx;
8 use syntax_pos::Span;
9 use syntax_pos::symbol::keywords;
10
11 use crate::dataflow::move_paths::InitLocation;
12 use crate::borrow_check::MirBorrowckCtxt;
13 use crate::util::borrowck_errors::{BorrowckErrors, Origin};
14 use crate::util::collect_writes::FindAssignments;
15 use crate::util::suggest_ref_mut;
16 use rustc_errors::Applicability;
17
18 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
19 pub(super) enum AccessKind {
20     MutableBorrow,
21     Mutate,
22     Move,
23 }
24
25 impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
26     pub(super) fn report_mutability_error(
27         &mut self,
28         access_place: &Place<'tcx>,
29         span: Span,
30         the_place_err: &Place<'tcx>,
31         error_access: AccessKind,
32         location: Location,
33     ) {
34         debug!(
35             "report_mutability_error(\
36                 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
37             )",
38             access_place, span, the_place_err, error_access, location,
39         );
40
41         let mut err;
42         let item_msg;
43         let reason;
44         let access_place_desc = self.describe_place(access_place);
45         debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
46
47         match the_place_err {
48             Place::Local(local) => {
49                 item_msg = format!("`{}`", access_place_desc.unwrap());
50                 if let Place::Local(_) = access_place {
51                     reason = ", as it is not declared as mutable".to_string();
52                 } else {
53                     let name = self.mir.local_decls[*local]
54                         .name
55                         .expect("immutable unnamed local");
56                     reason = format!(", as `{}` is not declared as mutable", name);
57                 }
58             }
59
60             Place::Projection(box Projection {
61                 base,
62                 elem: ProjectionElem::Field(upvar_index, _),
63             }) => {
64                 debug_assert!(is_closure_or_generator(
65                     base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx)
66                 ));
67
68                 item_msg = format!("`{}`", access_place_desc.unwrap());
69                 if access_place.is_upvar_field_projection(self.mir, &self.infcx.tcx).is_some() {
70                     reason = ", as it is not declared as mutable".to_string();
71                 } else {
72                     let name = self.mir.upvar_decls[upvar_index.index()].debug_name;
73                     reason = format!(", as `{}` is not declared as mutable", name);
74                 }
75             }
76
77             Place::Projection(box Projection {
78                 base,
79                 elem: ProjectionElem::Deref,
80             }) => {
81                 if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() {
82                     item_msg = format!("`{}`", access_place_desc.unwrap());
83                     debug_assert!(self.mir.local_decls[Local::new(1)].ty.is_region_ptr());
84                     debug_assert!(is_closure_or_generator(
85                         the_place_err.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx)
86                     ));
87
88                     reason = if access_place.is_upvar_field_projection(self.mir,
89                                                                        &self.infcx.tcx).is_some() {
90                         ", as it is a captured variable in a `Fn` closure".to_string()
91                     } else {
92                         ", as `Fn` closures cannot mutate their captured variables".to_string()
93                     }
94                 } else if {
95                     if let Place::Local(local) = *base {
96                         if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
97                             = self.mir.local_decls[local].is_user_variable {
98                                 true
99                         } else {
100                             false
101                         }
102                     } else {
103                         false
104                     }
105                 } {
106                     item_msg = format!("`{}`", access_place_desc.unwrap());
107                     reason = ", as it is immutable for the pattern guard".to_string();
108                 } else {
109                     let pointer_type =
110                         if base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx).is_region_ptr() {
111                             "`&` reference"
112                         } else {
113                             "`*const` pointer"
114                         };
115                     if let Some(desc) = access_place_desc {
116                         item_msg = format!("`{}`", desc);
117                         reason = match error_access {
118                             AccessKind::Move |
119                             AccessKind::Mutate => format!(" which is behind a {}", pointer_type),
120                             AccessKind::MutableBorrow => {
121                                 format!(", as it is behind a {}", pointer_type)
122                             }
123                         }
124                     } else {
125                         item_msg = format!("data in a {}", pointer_type);
126                         reason = String::new();
127                     }
128                 }
129             }
130
131             Place::Promoted(_) => unreachable!(),
132
133             Place::Static(box Static { def_id, ty: _ }) => {
134                 if let Place::Static(_) = access_place {
135                     item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
136                     reason = String::new();
137                 } else {
138                     item_msg = format!("`{}`", access_place_desc.unwrap());
139                     let static_name = &self.infcx.tcx.item_name(*def_id);
140                     reason = format!(", as `{}` is an immutable static item", static_name);
141                 }
142             }
143
144             Place::Projection(box Projection {
145                 base: _,
146                 elem: ProjectionElem::Index(_),
147             })
148             | Place::Projection(box Projection {
149                 base: _,
150                 elem: ProjectionElem::ConstantIndex { .. },
151             })
152             | Place::Projection(box Projection {
153                 base: _,
154                 elem: ProjectionElem::Subslice { .. },
155             })
156             | Place::Projection(box Projection {
157                 base: _,
158                 elem: ProjectionElem::Downcast(..),
159             }) => bug!("Unexpected immutable place."),
160         }
161
162         debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
163
164         // `act` and `acted_on` are strings that let us abstract over
165         // the verbs used in some diagnostic messages.
166         let act;
167         let acted_on;
168
169         let span = match error_access {
170             AccessKind::Move => {
171                 err = self.infcx.tcx
172                     .cannot_move_out_of(span, &(item_msg + &reason), Origin::Mir);
173                 err.span_label(span, "cannot move");
174                 err.buffer(&mut self.errors_buffer);
175                 return;
176             }
177             AccessKind::Mutate => {
178                 err = self.infcx.tcx
179                     .cannot_assign(span, &(item_msg + &reason), Origin::Mir);
180                 act = "assign";
181                 acted_on = "written";
182                 span
183             }
184             AccessKind::MutableBorrow => {
185                 act = "borrow as mutable";
186                 acted_on = "borrowed as mutable";
187
188                 let borrow_spans = self.borrow_spans(span, location);
189                 let borrow_span = borrow_spans.args_or_use();
190                 err = self.infcx.tcx.cannot_borrow_path_as_mutable_because(
191                     borrow_span,
192                     &item_msg,
193                     &reason,
194                     Origin::Mir,
195                 );
196                 borrow_spans.var_span_label(
197                     &mut err,
198                     format!(
199                         "mutable borrow occurs due to use of `{}` in closure",
200                         // always Some() if the message is printed.
201                         self.describe_place(access_place).unwrap_or_default(),
202                     )
203                 );
204                 borrow_span
205             }
206         };
207
208         debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
209
210         match the_place_err {
211             // Suggest making an existing shared borrow in a struct definition a mutable borrow.
212             //
213             // This is applicable when we have a deref of a field access to a deref of a local -
214             // something like `*((*_1).0`. The local that we get will be a reference to the
215             // struct we've got a field access of (it must be a reference since there's a deref
216             // after the field access).
217             Place::Projection(box Projection {
218                 base: Place::Projection(box Projection {
219                     base: Place::Projection(box Projection {
220                         base,
221                         elem: ProjectionElem::Deref,
222                     }),
223                     elem: ProjectionElem::Field(field, _),
224                 }),
225                 elem: ProjectionElem::Deref,
226             }) => {
227                 err.span_label(span, format!("cannot {ACT}", ACT = act));
228
229                 if let Some((span, message)) = annotate_struct_field(
230                     self.infcx.tcx,
231                     base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx),
232                     field,
233                 ) {
234                     err.span_suggestion(
235                         span,
236                         "consider changing this to be mutable",
237                         message,
238                         Applicability::MaybeIncorrect,
239                     );
240                 }
241             },
242
243             // Suggest removing a `&mut` from the use of a mutable reference.
244             Place::Local(local)
245                 if {
246                     self.mir.local_decls.get(*local).map(|local_decl| {
247                         if let ClearCrossCrate::Set(
248                             mir::BindingForm::ImplicitSelf(kind)
249                         ) = local_decl.is_user_variable.as_ref().unwrap() {
250                             // Check if the user variable is a `&mut self` and we can therefore
251                             // suggest removing the `&mut`.
252                             //
253                             // Deliberately fall into this case for all implicit self types,
254                             // so that we don't fall in to the next case with them.
255                             *kind == mir::ImplicitSelfKind::MutRef
256                         } else if Some(keywords::SelfLower.name()) == local_decl.name {
257                             // Otherwise, check if the name is the self kewyord - in which case
258                             // we have an explicit self. Do the same thing in this case and check
259                             // for a `self: &mut Self` to suggest removing the `&mut`.
260                             if let ty::TyKind::Ref(
261                                 _, _, hir::Mutability::MutMutable
262                             ) = local_decl.ty.sty {
263                                 true
264                             } else {
265                                 false
266                             }
267                         } else {
268                             false
269                         }
270                     }).unwrap_or(false)
271                 } =>
272             {
273                 err.span_label(span, format!("cannot {ACT}", ACT = act));
274                 err.span_label(span, "try removing `&mut` here");
275             },
276
277             // We want to suggest users use `let mut` for local (user
278             // variable) mutations...
279             Place::Local(local) if self.mir.local_decls[*local].can_be_made_mutable() => {
280                 // ... but it doesn't make sense to suggest it on
281                 // variables that are `ref x`, `ref mut x`, `&self`,
282                 // or `&mut self` (such variables are simply not
283                 // mutable).
284                 let local_decl = &self.mir.local_decls[*local];
285                 assert_eq!(local_decl.mutability, Mutability::Not);
286
287                 err.span_label(span, format!("cannot {ACT}", ACT = act));
288                 err.span_suggestion(
289                     local_decl.source_info.span,
290                     "consider changing this to be mutable",
291                     format!("mut {}", local_decl.name.unwrap()),
292                     Applicability::MachineApplicable,
293                 );
294             }
295
296             // Also suggest adding mut for upvars
297             Place::Projection(box Projection {
298                 base,
299                 elem: ProjectionElem::Field(upvar_index, _),
300             }) => {
301                 debug_assert!(is_closure_or_generator(
302                     base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx)
303                 ));
304
305                 err.span_label(span, format!("cannot {ACT}", ACT = act));
306
307                 let upvar_hir_id = self.mir.upvar_decls[upvar_index.index()]
308                     .var_hir_id
309                     .assert_crate_local();
310                 let upvar_node_id = self.infcx.tcx.hir().hir_to_node_id(upvar_hir_id);
311                 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_node_id) {
312                     if let hir::PatKind::Binding(
313                         hir::BindingAnnotation::Unannotated,
314                         _,
315                         _,
316                         upvar_ident,
317                         _,
318                     ) = pat.node
319                     {
320                         err.span_suggestion(
321                             upvar_ident.span,
322                             "consider changing this to be mutable",
323                             format!("mut {}", upvar_ident.name),
324                             Applicability::MachineApplicable,
325                         );
326                     }
327                 }
328             }
329
330             // complete hack to approximate old AST-borrowck
331             // diagnostic: if the span starts with a mutable borrow of
332             // a local variable, then just suggest the user remove it.
333             Place::Local(_)
334                 if {
335                     if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
336                         snippet.starts_with("&mut ")
337                     } else {
338                         false
339                     }
340                 } =>
341             {
342                 err.span_label(span, format!("cannot {ACT}", ACT = act));
343                 err.span_label(span, "try removing `&mut` here");
344             }
345
346             Place::Projection(box Projection {
347                 base: Place::Local(local),
348                 elem: ProjectionElem::Deref,
349             }) if {
350                 if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard)) =
351                     self.mir.local_decls[*local].is_user_variable
352                 {
353                     true
354                 } else {
355                     false
356                 }
357             } =>
358             {
359                 err.span_label(span, format!("cannot {ACT}", ACT = act));
360                 err.note(
361                     "variables bound in patterns are immutable until the end of the pattern guard",
362                 );
363             }
364
365             // We want to point out when a `&` can be readily replaced
366             // with an `&mut`.
367             //
368             // FIXME: can this case be generalized to work for an
369             // arbitrary base for the projection?
370             Place::Projection(box Projection {
371                 base: Place::Local(local),
372                 elem: ProjectionElem::Deref,
373             }) if self.mir.local_decls[*local].is_user_variable.is_some() =>
374             {
375                 let local_decl = &self.mir.local_decls[*local];
376                 let suggestion = match local_decl.is_user_variable.as_ref().unwrap() {
377                     ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_)) => {
378                         Some(suggest_ampmut_self(self.infcx.tcx, local_decl))
379                     }
380
381                     ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
382                         binding_mode: ty::BindingMode::BindByValue(_),
383                         opt_ty_info,
384                         ..
385                     })) => Some(suggest_ampmut(
386                         self.infcx.tcx,
387                         self.mir,
388                         *local,
389                         local_decl,
390                         *opt_ty_info,
391                     )),
392
393                     ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
394                         binding_mode: ty::BindingMode::BindByReference(_),
395                         ..
396                     })) => {
397                         let pattern_span = local_decl.source_info.span;
398                         suggest_ref_mut(self.infcx.tcx, pattern_span)
399                             .map(|replacement| (pattern_span, replacement))
400                     }
401
402                     ClearCrossCrate::Set(mir::BindingForm::RefForGuard) => unreachable!(),
403
404                     ClearCrossCrate::Clear => bug!("saw cleared local state"),
405                 };
406
407                 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
408                     ("&", "reference")
409                 } else {
410                     ("*const", "pointer")
411                 };
412
413                 if let Some((err_help_span, suggested_code)) = suggestion {
414                     err.span_suggestion(
415                         err_help_span,
416                         &format!("consider changing this to be a mutable {}", pointer_desc),
417                         suggested_code,
418                         Applicability::MachineApplicable,
419                     );
420                 }
421
422                 if let Some(name) = local_decl.name {
423                     err.span_label(
424                         span,
425                         format!(
426                             "`{NAME}` is a `{SIGIL}` {DESC}, \
427                              so the data it refers to cannot be {ACTED_ON}",
428                             NAME = name,
429                             SIGIL = pointer_sigil,
430                             DESC = pointer_desc,
431                             ACTED_ON = acted_on
432                         ),
433                     );
434                 } else {
435                     err.span_label(
436                         span,
437                         format!(
438                             "cannot {ACT} through `{SIGIL}` {DESC}",
439                             ACT = act,
440                             SIGIL = pointer_sigil,
441                             DESC = pointer_desc
442                         ),
443                     );
444                 }
445             }
446
447             Place::Projection(box Projection {
448                 base,
449                 elem: ProjectionElem::Deref,
450             }) if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() =>
451             {
452                 err.span_label(span, format!("cannot {ACT}", ACT = act));
453                 err.span_help(
454                     self.mir.span,
455                     "consider changing this to accept closures that implement `FnMut`"
456                 );
457             }
458
459             Place::Projection(box Projection {
460                 base: Place::Local(local),
461                 elem: ProjectionElem::Deref,
462             })  if error_access == AccessKind::MutableBorrow => {
463                 err.span_label(span, format!("cannot {ACT}", ACT = act));
464
465                 let mpi = self.move_data.rev_lookup.find_local(*local);
466                 for i in self.move_data.init_path_map[mpi].iter() {
467                     if let InitLocation::Statement(location) = self.move_data.inits[*i].location {
468                         if let Some(
469                             Terminator {
470                                 kind: TerminatorKind::Call {
471                                     func: Operand::Constant(box Constant {
472                                         literal: ty::LazyConst::Evaluated(Const {
473                                             ty: &TyS {
474                                                 sty: TyKind::FnDef(id, substs),
475                                                 ..
476                                             },
477                                             ..
478                                         }),
479                                         ..
480                                     }),
481                                     ..
482                                 },
483                                 ..
484                             }
485                         ) = &self.mir.basic_blocks()[location.block].terminator {
486                             let index_trait = self.infcx.tcx.lang_items().index_trait();
487                             if self.infcx.tcx.parent(id) == index_trait {
488                                 let mut found = false;
489                                 self.infcx.tcx.for_each_relevant_impl(
490                                     self.infcx.tcx.lang_items().index_mut_trait().unwrap(),
491                                     substs.type_at(0),
492                                     |_relevant_impl| {
493                                         found = true;
494                                     }
495                                 );
496
497                                 let extra = if found {
498                                     String::new()
499                                 } else {
500                                     format!(", but it is not implemented for `{}`",
501                                             substs.type_at(0))
502                                 };
503
504                                 err.help(
505                                     &format!(
506                                         "trait `IndexMut` is required to modify indexed content{}",
507                                          extra,
508                                     ),
509                                 );
510                             }
511                         }
512                     }
513                 }
514             }
515
516             _ => {
517                 err.span_label(span, format!("cannot {ACT}", ACT = act));
518             }
519         }
520
521         err.buffer(&mut self.errors_buffer);
522     }
523 }
524
525 fn suggest_ampmut_self<'cx, 'gcx, 'tcx>(
526     tcx: TyCtxt<'cx, 'gcx, 'tcx>,
527     local_decl: &mir::LocalDecl<'tcx>,
528 ) -> (Span, String) {
529     let sp = local_decl.source_info.span;
530     (sp, match tcx.sess.source_map().span_to_snippet(sp) {
531         Ok(snippet) => {
532             let lt_pos = snippet.find('\'');
533             if let Some(lt_pos) = lt_pos {
534                 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
535             } else {
536                 "&mut self".to_string()
537             }
538         }
539         _ => "&mut self".to_string()
540     })
541 }
542
543 // When we want to suggest a user change a local variable to be a `&mut`, there
544 // are three potential "obvious" things to highlight:
545 //
546 // let ident [: Type] [= RightHandSideExpression];
547 //     ^^^^^    ^^^^     ^^^^^^^^^^^^^^^^^^^^^^^
548 //     (1.)     (2.)              (3.)
549 //
550 // We can always fallback on highlighting the first. But chances are good that
551 // the user experience will be better if we highlight one of the others if possible;
552 // for example, if the RHS is present and the Type is not, then the type is going to
553 // be inferred *from* the RHS, which means we should highlight that (and suggest
554 // that they borrow the RHS mutably).
555 //
556 // This implementation attempts to emulate AST-borrowck prioritization
557 // by trying (3.), then (2.) and finally falling back on (1.).
558 fn suggest_ampmut<'cx, 'gcx, 'tcx>(
559     tcx: TyCtxt<'cx, 'gcx, 'tcx>,
560     mir: &Mir<'tcx>,
561     local: Local,
562     local_decl: &mir::LocalDecl<'tcx>,
563     opt_ty_info: Option<Span>,
564 ) -> (Span, String) {
565     let locations = mir.find_assignments(local);
566     if !locations.is_empty() {
567         let assignment_rhs_span = mir.source_info(locations[0]).span;
568         if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
569             if let (true, Some(ws_pos)) = (
570                 src.starts_with("&'"),
571                 src.find(|c: char| -> bool { c.is_whitespace() }),
572             ) {
573                 let lt_name = &src[1..ws_pos];
574                 let ty = &src[ws_pos..];
575                 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
576             } else if src.starts_with('&') {
577                 let borrowed_expr = &src[1..];
578                 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
579             }
580         }
581     }
582
583     let highlight_span = match opt_ty_info {
584         // if this is a variable binding with an explicit type,
585         // try to highlight that for the suggestion.
586         Some(ty_span) => ty_span,
587
588         // otherwise, just highlight the span associated with
589         // the (MIR) LocalDecl.
590         None => local_decl.source_info.span,
591     };
592
593     if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
594         if let (true, Some(ws_pos)) = (
595             src.starts_with("&'"),
596             src.find(|c: char| -> bool { c.is_whitespace() }),
597         ) {
598             let lt_name = &src[1..ws_pos];
599             let ty = &src[ws_pos..];
600             return (highlight_span, format!("&{} mut{}", lt_name, ty));
601         }
602     }
603
604     let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
605     assert_eq!(ty_mut.mutbl, hir::MutImmutable);
606     (highlight_span,
607      if local_decl.ty.is_region_ptr() {
608          format!("&mut {}", ty_mut.ty)
609      } else {
610          format!("*mut {}", ty_mut.ty)
611      })
612 }
613
614 fn is_closure_or_generator(ty: ty::Ty<'_>) -> bool {
615     ty.is_closure() || ty.is_generator()
616 }
617
618 /// Adds a suggestion to a struct definition given a field access to a local.
619 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
620 ///
621 /// ```text
622 /// LL |     s: &'a String
623 ///    |        ---------- use `&'a mut String` here to make mutable
624 /// ```
625 fn annotate_struct_field(
626     tcx: TyCtxt<'cx, 'gcx, 'tcx>,
627     ty: ty::Ty<'tcx>,
628     field: &mir::Field,
629 ) -> Option<(Span, String)> {
630     // Expect our local to be a reference to a struct of some kind.
631     if let ty::TyKind::Ref(_, ty, _) = ty.sty {
632         if let ty::TyKind::Adt(def, _) = ty.sty {
633             let field = def.all_fields().nth(field.index())?;
634             // Use the HIR types to construct the diagnostic message.
635             let node_id = tcx.hir().as_local_node_id(field.did)?;
636             let node = tcx.hir().find(node_id)?;
637             // Now we're dealing with the actual struct that we're going to suggest a change to,
638             // we can expect a field that is an immutable reference to a type.
639             if let hir::Node::Field(field) = node {
640                 if let hir::TyKind::Rptr(lifetime, hir::MutTy {
641                     mutbl: hir::Mutability::MutImmutable,
642                     ref ty
643                 }) = field.ty.node {
644                     // Get the snippets in two parts - the named lifetime (if there is one) and
645                     // type being referenced, that way we can reconstruct the snippet without loss
646                     // of detail.
647                     let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
648                     let lifetime_snippet = if !lifetime.is_elided() {
649                         format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
650                     } else {
651                         String::new()
652                     };
653
654                     return Some((
655                         field.ty.span,
656                         format!(
657                             "&{}mut {}",
658                             lifetime_snippet, &*type_snippet,
659                         ),
660                     ));
661                 }
662             }
663         }
664     }
665
666     None
667 }