]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
Rollup merge of #55169 - raphlinus:copysign, r=joshtriplett
[rust.git] / src / librustc_mir / borrow_check / nll / region_infer / error_reporting / mod.rs
1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use borrow_check::nll::constraints::{OutlivesConstraint};
12 use borrow_check::nll::region_infer::RegionInferenceContext;
13 use borrow_check::nll::region_infer::error_reporting::region_name::RegionNameSource;
14 use borrow_check::nll::type_check::Locations;
15 use borrow_check::nll::universal_regions::DefiningTy;
16 use rustc::hir::def_id::DefId;
17 use rustc::infer::error_reporting::nice_region_error::NiceRegionError;
18 use rustc::infer::InferCtxt;
19 use rustc::mir::{ConstraintCategory, Location, Mir};
20 use rustc::ty::{self, RegionVid};
21 use rustc_data_structures::indexed_vec::IndexVec;
22 use rustc_errors::{Diagnostic, DiagnosticBuilder};
23 use std::collections::VecDeque;
24 use syntax::symbol::keywords;
25 use syntax_pos::Span;
26 use syntax::errors::Applicability;
27
28 mod region_name;
29 mod var_name;
30
31 use self::region_name::RegionName;
32
33 trait ConstraintDescription {
34     fn description(&self) -> &'static str;
35 }
36
37 impl ConstraintDescription for ConstraintCategory {
38     fn description(&self) -> &'static str {
39         // Must end with a space. Allows for empty names to be provided.
40         match self {
41             ConstraintCategory::Assignment => "assignment ",
42             ConstraintCategory::Return => "returning this value ",
43             ConstraintCategory::Cast => "cast ",
44             ConstraintCategory::CallArgument => "argument ",
45             ConstraintCategory::TypeAnnotation => "type annotation ",
46             ConstraintCategory::ClosureBounds => "closure body ",
47             ConstraintCategory::SizedBound => "proving this value is `Sized` ",
48             ConstraintCategory::CopyBound => "copying this value ",
49             ConstraintCategory::OpaqueType => "opaque type ",
50             ConstraintCategory::Boring
51             | ConstraintCategory::BoringNoLocation
52             | ConstraintCategory::Internal => "",
53         }
54     }
55 }
56
57 #[derive(Copy, Clone, PartialEq, Eq)]
58 enum Trace {
59     StartRegion,
60     FromOutlivesConstraint(OutlivesConstraint),
61     NotVisited,
62 }
63
64 impl<'tcx> RegionInferenceContext<'tcx> {
65     /// Tries to find the best constraint to blame for the fact that
66     /// `R: from_region`, where `R` is some region that meets
67     /// `target_test`. This works by following the constraint graph,
68     /// creating a constraint path that forces `R` to outlive
69     /// `from_region`, and then finding the best choices within that
70     /// path to blame.
71     fn best_blame_constraint(
72         &self,
73         mir: &Mir<'tcx>,
74         from_region: RegionVid,
75         target_test: impl Fn(RegionVid) -> bool,
76     ) -> (ConstraintCategory, Span, RegionVid) {
77         debug!("best_blame_constraint(from_region={:?})", from_region);
78
79         // Find all paths
80         let (path, target_region) = self
81             .find_constraint_paths_between_regions(from_region, target_test)
82             .unwrap();
83         debug!(
84             "best_blame_constraint: path={:#?}",
85             path.iter()
86                 .map(|&c| format!(
87                     "{:?} ({:?}: {:?})",
88                     c,
89                     self.constraint_sccs.scc(c.sup),
90                     self.constraint_sccs.scc(c.sub),
91                 ))
92                 .collect::<Vec<_>>()
93         );
94
95         // Classify each of the constraints along the path.
96         let mut categorized_path: Vec<(ConstraintCategory, Span)> = path
97             .iter()
98             .map(|constraint| {
99                 if constraint.category == ConstraintCategory::ClosureBounds {
100                     self.retrieve_closure_constraint_info(mir, &constraint)
101                 } else {
102                     (constraint.category, constraint.locations.span(mir))
103                 }
104             })
105             .collect();
106         debug!(
107             "best_blame_constraint: categorized_path={:#?}",
108             categorized_path
109         );
110
111         // To find the best span to cite, we first try to look for the
112         // final constraint that is interesting and where the `sup` is
113         // not unified with the ultimate target region. The reason
114         // for this is that we have a chain of constraints that lead
115         // from the source to the target region, something like:
116         //
117         //    '0: '1 ('0 is the source)
118         //    '1: '2
119         //    '2: '3
120         //    '3: '4
121         //    '4: '5
122         //    '5: '6 ('6 is the target)
123         //
124         // Some of those regions are unified with `'6` (in the same
125         // SCC).  We want to screen those out. After that point, the
126         // "closest" constraint we have to the end is going to be the
127         // most likely to be the point where the value escapes -- but
128         // we still want to screen for an "interesting" point to
129         // highlight (e.g., a call site or something).
130         let target_scc = self.constraint_sccs.scc(target_region);
131         let best_choice = (0..path.len()).rev().find(|&i| {
132             let constraint = path[i];
133
134             let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup);
135
136             match categorized_path[i].0 {
137                 ConstraintCategory::OpaqueType
138                 | ConstraintCategory::Boring
139                 | ConstraintCategory::BoringNoLocation
140                 | ConstraintCategory::Internal => false,
141                 _ => constraint_sup_scc != target_scc,
142             }
143         });
144         if let Some(i) = best_choice {
145             let (category, span) = categorized_path[i];
146             return (category, span, target_region);
147         }
148
149         // If that search fails, that is.. unusual. Maybe everything
150         // is in the same SCC or something. In that case, find what
151         // appears to be the most interesting point to report to the
152         // user via an even more ad-hoc guess.
153         categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0));
154         debug!("best_blame_constraint: sorted_path={:#?}", categorized_path);
155
156         let &(category, span) = categorized_path.first().unwrap();
157
158         (category, span, target_region)
159     }
160
161     /// Walks the graph of constraints (where `'a: 'b` is considered
162     /// an edge `'a -> 'b`) to find all paths from `from_region` to
163     /// `to_region`. The paths are accumulated into the vector
164     /// `results`. The paths are stored as a series of
165     /// `ConstraintIndex` values -- in other words, a list of *edges*.
166     ///
167     /// Returns: a series of constraints as well as the region `R`
168     /// that passed the target test.
169     fn find_constraint_paths_between_regions(
170         &self,
171         from_region: RegionVid,
172         target_test: impl Fn(RegionVid) -> bool,
173     ) -> Option<(Vec<OutlivesConstraint>, RegionVid)> {
174         let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions);
175         context[from_region] = Trace::StartRegion;
176
177         // Use a deque so that we do a breadth-first search. We will
178         // stop at the first match, which ought to be the shortest
179         // path (fewest constraints).
180         let mut deque = VecDeque::new();
181         deque.push_back(from_region);
182
183         while let Some(r) = deque.pop_front() {
184             // Check if we reached the region we were looking for. If so,
185             // we can reconstruct the path that led to it and return it.
186             if target_test(r) {
187                 let mut result = vec![];
188                 let mut p = r;
189                 loop {
190                     match context[p] {
191                         Trace::NotVisited => {
192                             bug!("found unvisited region {:?} on path to {:?}", p, r)
193                         }
194                         Trace::FromOutlivesConstraint(c) => {
195                             result.push(c);
196                             p = c.sup;
197                         }
198
199                         Trace::StartRegion => {
200                             result.reverse();
201                             return Some((result, r));
202                         }
203                     }
204                 }
205             }
206
207             // Otherwise, walk over the outgoing constraints and
208             // enqueue any regions we find, keeping track of how we
209             // reached them.
210             let fr_static = self.universal_regions.fr_static;
211             for constraint in self.constraint_graph.outgoing_edges(r,
212                                                                    &self.constraints,
213                                                                    fr_static) {
214                 assert_eq!(constraint.sup, r);
215                 let sub_region = constraint.sub;
216                 if let Trace::NotVisited = context[sub_region] {
217                     context[sub_region] = Trace::FromOutlivesConstraint(constraint);
218                     deque.push_back(sub_region);
219                 }
220             }
221         }
222
223         None
224     }
225
226     /// Report an error because the universal region `fr` was required to outlive
227     /// `outlived_fr` but it is not known to do so. For example:
228     ///
229     /// ```
230     /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
231     /// ```
232     ///
233     /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
234     pub(super) fn report_error(
235         &self,
236         mir: &Mir<'tcx>,
237         infcx: &InferCtxt<'_, '_, 'tcx>,
238         mir_def_id: DefId,
239         fr: RegionVid,
240         outlived_fr: RegionVid,
241         errors_buffer: &mut Vec<Diagnostic>,
242     ) {
243         debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
244
245         let (category, span, _) = self.best_blame_constraint(
246             mir,
247             fr,
248             |r| r == outlived_fr
249         );
250
251         // Check if we can use one of the "nice region errors".
252         if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
253             let tables = infcx.tcx.typeck_tables_of(mir_def_id);
254             let nice = NiceRegionError::new_from_span(infcx.tcx, span, o, f, Some(tables));
255             if let Some(_error_reported) = nice.try_report_from_nll() {
256                 return;
257             }
258         }
259
260         let (fr_is_local, outlived_fr_is_local): (bool, bool) = (
261             self.universal_regions.is_local_free_region(fr),
262             self.universal_regions.is_local_free_region(outlived_fr),
263         );
264
265         debug!("report_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}",
266                fr_is_local, outlived_fr_is_local, category);
267         match (category, fr_is_local, outlived_fr_is_local) {
268             (ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(infcx, fr) =>
269                 self.report_fnmut_error(mir, infcx, mir_def_id, fr, outlived_fr, span,
270                                         errors_buffer),
271             (ConstraintCategory::Assignment, true, false) |
272             (ConstraintCategory::CallArgument, true, false) =>
273                 self.report_escaping_data_error(mir, infcx, mir_def_id, fr, outlived_fr,
274                                                 category, span, errors_buffer),
275             _ =>
276                 self.report_general_error(mir, infcx, mir_def_id, fr, fr_is_local,
277                                           outlived_fr, outlived_fr_is_local,
278                                           category, span, errors_buffer),
279         };
280     }
281
282     /// Report a specialized error when `FnMut` closures return a reference to a captured variable.
283     /// This function expects `fr` to be local and `outlived_fr` to not be local.
284     ///
285     /// ```text
286     /// error: captured variable cannot escape `FnMut` closure body
287     ///   --> $DIR/issue-53040.rs:15:8
288     ///    |
289     /// LL |     || &mut v;
290     ///    |     -- ^^^^^^ creates a reference to a captured variable which escapes the closure body
291     ///    |     |
292     ///    |     inferred to be a `FnMut` closure
293     ///    |
294     ///    = note: `FnMut` closures only have access to their captured variables while they are
295     ///            executing...
296     ///    = note: ...therefore, returned references to captured variables will escape the closure
297     /// ```
298     fn report_fnmut_error(
299         &self,
300         mir: &Mir<'tcx>,
301         infcx: &InferCtxt<'_, '_, 'tcx>,
302         mir_def_id: DefId,
303         _fr: RegionVid,
304         outlived_fr: RegionVid,
305         span: Span,
306         errors_buffer: &mut Vec<Diagnostic>,
307     ) {
308         let mut diag = infcx.tcx.sess.struct_span_err(
309             span,
310             "captured variable cannot escape `FnMut` closure body",
311         );
312
313         // We should check if the return type of this closure is in fact a closure - in that
314         // case, we can special case the error further.
315         let return_type_is_closure = self.universal_regions.unnormalized_output_ty.is_closure();
316         let message = if return_type_is_closure {
317             "returns a closure that contains a reference to a captured variable, which then \
318              escapes the closure body"
319         } else {
320             "returns a reference to a captured variable which escapes the closure body"
321         };
322
323         diag.span_label(
324             span,
325             message,
326         );
327
328         match self.give_region_a_name(infcx, mir, mir_def_id, outlived_fr, &mut 1).source {
329             RegionNameSource::NamedEarlyBoundRegion(fr_span) |
330             RegionNameSource::NamedFreeRegion(fr_span) |
331             RegionNameSource::SynthesizedFreeEnvRegion(fr_span, _) |
332             RegionNameSource::CannotMatchHirTy(fr_span, _) |
333             RegionNameSource::MatchedHirTy(fr_span) |
334             RegionNameSource::MatchedAdtAndSegment(fr_span) |
335             RegionNameSource::AnonRegionFromUpvar(fr_span, _) |
336             RegionNameSource::AnonRegionFromOutput(fr_span, _, _) => {
337                 diag.span_label(fr_span, "inferred to be a `FnMut` closure");
338             },
339             _ => {},
340         }
341
342         diag.note("`FnMut` closures only have access to their captured variables while they are \
343                    executing...");
344         diag.note("...therefore, they cannot allow references to captured variables to escape");
345
346         diag.buffer(errors_buffer);
347     }
348
349     /// Reports a error specifically for when data is escaping a closure.
350     ///
351     /// ```text
352     /// error: borrowed data escapes outside of function
353     ///   --> $DIR/lifetime-bound-will-change-warning.rs:44:5
354     ///    |
355     /// LL | fn test2<'a>(x: &'a Box<Fn()+'a>) {
356     ///    |              - `x` is a reference that is only valid in the function body
357     /// LL |     // but ref_obj will not, so warn.
358     /// LL |     ref_obj(x)
359     ///    |     ^^^^^^^^^^ `x` escapes the function body here
360     /// ```
361     fn report_escaping_data_error(
362         &self,
363         mir: &Mir<'tcx>,
364         infcx: &InferCtxt<'_, '_, 'tcx>,
365         mir_def_id: DefId,
366         fr: RegionVid,
367         outlived_fr: RegionVid,
368         category: ConstraintCategory,
369         span: Span,
370         errors_buffer: &mut Vec<Diagnostic>,
371     ) {
372         let fr_name_and_span = self.get_var_name_and_span_for_region(infcx.tcx, mir, fr);
373         let outlived_fr_name_and_span =
374             self.get_var_name_and_span_for_region(infcx.tcx, mir, outlived_fr);
375
376         let escapes_from = if infcx.tcx.is_closure(mir_def_id) { "closure" } else { "function" };
377
378         // Revert to the normal error in these cases.
379         // Assignments aren't "escapes" in function items.
380         if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none())
381             || (category == ConstraintCategory::Assignment && escapes_from == "function")
382         {
383             return self.report_general_error(mir, infcx, mir_def_id,
384                                              fr, true, outlived_fr, false,
385                                              category, span, errors_buffer);
386         }
387
388         let mut diag = infcx.tcx.sess.struct_span_err(
389             span, &format!("borrowed data escapes outside of {}", escapes_from),
390         );
391
392         if let Some((Some(outlived_fr_name), outlived_fr_span)) = outlived_fr_name_and_span {
393             diag.span_label(
394                 outlived_fr_span,
395                 format!(
396                     "`{}` is declared here, outside of the {} body",
397                     outlived_fr_name, escapes_from
398                 ),
399             );
400         }
401
402         if let Some((Some(fr_name), fr_span)) = fr_name_and_span {
403             diag.span_label(
404                 fr_span,
405                 format!(
406                     "`{}` is a reference that is only valid in the {} body",
407                     fr_name, escapes_from
408                 ),
409             );
410
411             diag.span_label(span, format!("`{}` escapes the {} body here", fr_name, escapes_from));
412         }
413
414         diag.buffer(errors_buffer);
415     }
416
417     /// Reports a region inference error for the general case with named/synthesized lifetimes to
418     /// explain what is happening.
419     ///
420     /// ```text
421     /// error: unsatisfied lifetime constraints
422     ///   --> $DIR/regions-creating-enums3.rs:17:5
423     ///    |
424     /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> {
425     ///    |                -- -- lifetime `'b` defined here
426     ///    |                |
427     ///    |                lifetime `'a` defined here
428     /// LL |     ast::add(x, y)
429     ///    |     ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it
430     ///    |                    is returning data with lifetime `'b`
431     /// ```
432     fn report_general_error(
433         &self,
434         mir: &Mir<'tcx>,
435         infcx: &InferCtxt<'_, '_, 'tcx>,
436         mir_def_id: DefId,
437         fr: RegionVid,
438         fr_is_local: bool,
439         outlived_fr: RegionVid,
440         outlived_fr_is_local: bool,
441         category: ConstraintCategory,
442         span: Span,
443         errors_buffer: &mut Vec<Diagnostic>,
444     ) {
445         let mut diag = infcx.tcx.sess.struct_span_err(
446             span,
447             "unsatisfied lifetime constraints", // FIXME
448         );
449
450         let counter = &mut 1;
451         let fr_name = self.give_region_a_name(infcx, mir, mir_def_id, fr, counter);
452         fr_name.highlight_region_name(&mut diag);
453         let outlived_fr_name = self.give_region_a_name(
454             infcx, mir, mir_def_id, outlived_fr, counter);
455         outlived_fr_name.highlight_region_name(&mut diag);
456
457         let mir_def_name = if infcx.tcx.is_closure(mir_def_id) { "closure" } else { "function" };
458
459         match (category, outlived_fr_is_local, fr_is_local) {
460             (ConstraintCategory::Return, true, _) => {
461                 diag.span_label(span, format!(
462                     "{} was supposed to return data with lifetime `{}` but it is returning \
463                     data with lifetime `{}`",
464                     mir_def_name, outlived_fr_name, fr_name
465                 ));
466             },
467             _ => {
468                 diag.span_label(span, format!(
469                     "{}requires that `{}` must outlive `{}`",
470                     category.description(), fr_name, outlived_fr_name,
471                 ));
472             },
473         }
474
475         self.add_static_impl_trait_suggestion(
476             infcx, &mut diag, fr, fr_name, outlived_fr,
477         );
478
479         diag.buffer(errors_buffer);
480     }
481
482     /// Adds a suggestion to errors where a `impl Trait` is returned.
483     ///
484     /// ```text
485     /// help: to allow this impl Trait to capture borrowed data with lifetime `'1`, add `'_` as
486     ///       a constraint
487     ///    |
488     /// LL |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a {
489     ///    |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
490     /// ```
491     fn add_static_impl_trait_suggestion(
492         &self,
493         infcx: &InferCtxt<'_, '_, 'tcx>,
494         diag: &mut DiagnosticBuilder<'_>,
495         fr: RegionVid,
496         // We need to pass `fr_name` - computing it again will label it twice.
497         fr_name: RegionName,
498         outlived_fr: RegionVid,
499     ) {
500         if let (
501             Some(f),
502             Some(ty::RegionKind::ReStatic)
503         ) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
504             if let Some(ty::TyS {
505                 sty: ty::TyKind::Opaque(did, substs),
506                 ..
507             }) = infcx.tcx.is_suitable_region(f)
508                     .map(|r| r.def_id)
509                     .map(|id| infcx.tcx.return_type_impl_trait(id))
510                     .unwrap_or(None)
511             {
512                 // Check whether or not the impl trait return type is intended to capture
513                 // data with the static lifetime.
514                 //
515                 // eg. check for `impl Trait + 'static` instead of `impl Trait`.
516                 let has_static_predicate = {
517                     let predicates_of = infcx.tcx.predicates_of(*did);
518                     let bounds = predicates_of.instantiate(infcx.tcx, substs);
519
520                     let mut found = false;
521                     for predicate in bounds.predicates {
522                         if let ty::Predicate::TypeOutlives(binder) = predicate {
523                             if let ty::OutlivesPredicate(
524                                 _,
525                                 ty::RegionKind::ReStatic
526                             ) = binder.skip_binder() {
527                                 found = true;
528                                 break;
529                             }
530                         }
531                     }
532
533                     found
534                 };
535
536                 debug!("add_static_impl_trait_suggestion: has_static_predicate={:?}",
537                        has_static_predicate);
538                 let static_str = keywords::StaticLifetime.name();
539                 // If there is a static predicate, then the only sensible suggestion is to replace
540                 // fr with `'static`.
541                 if has_static_predicate {
542                     diag.help(
543                         &format!(
544                             "consider replacing `{}` with `{}`",
545                             fr_name, static_str,
546                         ),
547                     );
548                 } else {
549                     // Otherwise, we should suggest adding a constraint on the return type.
550                     let span = infcx.tcx.def_span(*did);
551                     if let Ok(snippet) = infcx.tcx.sess.source_map().span_to_snippet(span) {
552                         let suggestable_fr_name = if fr_name.was_named() {
553                             fr_name.to_string()
554                         } else {
555                             "'_".to_string()
556                         };
557
558                         diag.span_suggestion_with_applicability(
559                             span,
560                             &format!(
561                                 "to allow this impl Trait to capture borrowed data with lifetime \
562                                  `{}`, add `{}` as a constraint",
563                                 fr_name, suggestable_fr_name,
564                             ),
565                             format!("{} + {}", snippet, suggestable_fr_name),
566                             Applicability::MachineApplicable,
567                         );
568                     }
569                 }
570             }
571         }
572     }
573
574     // Finds some region R such that `fr1: R` and `R` is live at
575     // `elem`.
576     crate fn find_sub_region_live_at(&self, fr1: RegionVid, elem: Location) -> RegionVid {
577         // Find all paths
578         let (_path, r) =
579             self.find_constraint_paths_between_regions(fr1, |r| {
580                 self.liveness_constraints.contains(r, elem)
581             }).unwrap();
582         r
583     }
584
585     // Finds a good span to blame for the fact that `fr1` outlives `fr2`.
586     crate fn find_outlives_blame_span(
587         &self,
588         mir: &Mir<'tcx>,
589         fr1: RegionVid,
590         fr2: RegionVid,
591     ) -> (ConstraintCategory, Span) {
592         let (category, span, _) = self.best_blame_constraint(mir, fr1, |r| r == fr2);
593         (category, span)
594     }
595
596     fn retrieve_closure_constraint_info(
597         &self,
598         mir: &Mir<'tcx>,
599         constraint: &OutlivesConstraint
600     ) -> (ConstraintCategory, Span) {
601         let loc = match constraint.locations {
602             Locations::All(span) => return (constraint.category, span),
603             Locations::Single(loc) => loc,
604         };
605
606         let opt_span_category = self
607             .closure_bounds_mapping[&loc]
608             .get(&(constraint.sup, constraint.sub));
609         *opt_span_category.unwrap_or(&(constraint.category, mir.source_info(loc).span))
610     }
611
612     /// Returns `true` if a closure is inferred to be an `FnMut` closure.
613     crate fn is_closure_fn_mut(
614         &self,
615         infcx: &InferCtxt<'_, '_, 'tcx>,
616         fr: RegionVid,
617     ) -> bool {
618         if let Some(ty::ReFree(free_region)) = self.to_error_region(fr) {
619             if let ty::BoundRegion::BrEnv = free_region.bound_region {
620                 if let DefiningTy::Closure(def_id, substs) = self.universal_regions.defining_ty {
621                     let closure_kind_ty = substs.closure_kind_ty(def_id, infcx.tcx);
622                     return Some(ty::ClosureKind::FnMut) == closure_kind_ty.to_opt_closure_kind();
623                 }
624             }
625         }
626
627         false
628     }
629 }