Add the actual chain of projections to UserTypeProjection.

Update the existing NLL `patterns.rs` test accordingly.

includes changes addressing review feedback:

 * Added example to docs for `UserTypeProjections` illustrating how we
   build up multiple projections when descending into a pattern with
   type ascriptions.

 * Adapted niko's suggested docs for `UserTypeProjection`.

 * Factored out `projection_ty` from more general `projection_ty_core`
   (as a drive-by, made its callback an `FnMut`, as I discovered later
   that I need that).

 * Add note to docs that `PlaceTy.field_ty(..)` does not normalize its result.

 * Normalize as we project out `field_ty`.
This commit is contained in:
Felix S. Klock II 2018-10-22 22:50:10 +02:00
parent b569caf267
commit 740e8a3f37
13 changed files with 273 additions and 65 deletions

View file

@ -607,5 +607,5 @@ impl<'a, 'gcx> HashStable<StableHashingContext<'a>> for mir::UserTypeAnnotation<
}
}
impl_stable_hash_for!(struct mir::UserTypeProjection<'tcx> { base });
impl_stable_hash_for!(struct mir::UserTypeProjection<'tcx> { base, projs });
impl_stable_hash_for!(struct mir::UserTypeProjections<'tcx> { contents });

View file

@ -2456,6 +2456,31 @@ EnumLiftImpl! {
///
/// Its a collection because there can be multiple type ascriptions on
/// the path from the root of the pattern down to the binding itself.
///
/// An example:
///
/// ```rust
/// struct S<'a>((i32, &'a str), String);
/// let S((_, w): (i32, &'static str), _): S = ...;
/// // ------ ^^^^^^^^^^^^^^^^^^^ (1)
/// // --------------------------------- ^ (2)
/// ```
///
/// The highlights labelled `(1)` show the subpattern `(_, w)` being
/// ascribed the type `(i32, &'static str)`.
///
/// The highlights labelled `(2)` show the whole pattern being
/// ascribed the type `S`.
///
/// In this example, when we descend to `w`, we will have built up the
/// following two projected types:
///
/// * base: `S`, projection: `(base.0).1`
/// * base: `(i32, &'static str)`, projection: `base.1`
///
/// The first will lead to the constraint `w: &'1 str` (for some
/// inferred region `'1`). The second will lead to the constraint `w:
/// &'static str`.
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub struct UserTypeProjections<'tcx> {
pub(crate) contents: Vec<(UserTypeProjection<'tcx>, Span)>,
@ -2485,14 +2510,49 @@ impl<'tcx> UserTypeProjections<'tcx> {
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
/// Encodes the effect of a user-supplied type annotation on the
/// subcomponents of a pattern. The effect is determined by applying the
/// given list of proejctions to some underlying base type. Often,
/// the projection element list `projs` is empty, in which case this
/// directly encodes a type in `base`. But in the case of complex patterns with
/// subpatterns and bindings, we want to apply only a *part* of the type to a variable,
/// in which case the `projs` vector is used.
///
/// Examples:
///
/// * `let x: T = ...` -- here, the `projs` vector is empty.
///
/// * `let (x, _): T = ...` -- here, the `projs` vector would contain
/// `field[0]` (aka `.0`), indicating that the type of `s` is
/// determined by finding the type of the `.0` field from `T`.
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub struct UserTypeProjection<'tcx> {
pub base: UserTypeAnnotation<'tcx>,
pub projs: Vec<ProjectionElem<'tcx, (), ()>>,
}
BraceStructTypeFoldableImpl! {
impl<'tcx> TypeFoldable<'tcx> for UserTypeProjection<'tcx> {
base
impl<'tcx> TypeFoldable<'tcx> for UserTypeProjection<'tcx> {
fn super_fold_with<'gcx: 'tcx, F: TypeFolder<'gcx, 'tcx>>(&self, folder: &mut F) -> Self {
use mir::ProjectionElem::*;
let base = self.base.fold_with(folder);
let projs: Vec<_> = self.projs
.iter()
.map(|elem| {
match elem {
Deref => Deref,
Field(f, ()) => Field(f.clone(), ()),
Index(()) => Index(()),
elem => elem.clone(),
}})
.collect();
UserTypeProjection { base, projs }
}
fn super_visit_with<Vs: TypeVisitor<'tcx>>(&self, visitor: &mut Vs) -> bool {
self.base.visit_with(visitor)
// Note: there's nothing in `self.proj` to visit.
}
}

View file

@ -44,11 +44,59 @@ impl<'a, 'gcx, 'tcx> PlaceTy<'tcx> {
}
}
/// `place_ty.field_ty(tcx, f)` computes the type at a given field
/// of a record or enum-variant. (Most clients of `PlaceTy` can
/// instead just extract the relevant type directly from their
/// `PlaceElem`, but some instances of `ProjectionElem<V, T>` do
/// not carry a `Ty` for `T`.)
///
/// Note that the resulting type has not been normalized.
pub fn field_ty(self, tcx: TyCtxt<'a, 'gcx, 'tcx>, f: &Field) -> Ty<'tcx>
{
// Pass `0` here so it can be used as a "default" variant_index in first arm below
let answer = match (self, 0) {
(PlaceTy::Ty {
ty: &ty::TyS { sty: ty::TyKind::Adt(adt_def, substs), .. } }, variant_index) |
(PlaceTy::Downcast { adt_def, substs, variant_index }, _) => {
let variant_def = &adt_def.variants[variant_index];
let field_def = &variant_def.fields[f.index()];
field_def.ty(tcx, substs)
}
(PlaceTy::Ty { ty }, _) => {
match ty.sty {
ty::Tuple(ref tys) => tys[f.index()],
_ => bug!("extracting field of non-tuple non-adt: {:?}", self),
}
}
};
debug!("field_ty self: {:?} f: {:?} yields: {:?}", self, f, answer);
answer
}
/// Convenience wrapper around `projection_ty_core` for
/// `PlaceElem`, where we can just use the `Ty` that is already
/// stored inline on field projection elems.
pub fn projection_ty(self, tcx: TyCtxt<'a, 'gcx, 'tcx>,
elem: &PlaceElem<'tcx>)
-> PlaceTy<'tcx>
{
match *elem {
self.projection_ty_core(tcx, elem, |_, _, ty| ty)
}
/// `place_ty.projection_ty_core(tcx, elem, |...| { ... })`
/// projects `place_ty` onto `elem`, returning the appropriate
/// `Ty` or downcast variant corresponding to that projection.
/// The `handle_field` callback must map a `Field` to its `Ty`,
/// (which should be trivial when `T` = `Ty`).
pub fn projection_ty_core<V, T>(self,
tcx: TyCtxt<'a, 'gcx, 'tcx>,
elem: &ProjectionElem<'tcx, V, T>,
mut handle_field: impl FnMut(&Self, &Field, &T) -> Ty<'tcx>)
-> PlaceTy<'tcx>
where
V: ::std::fmt::Debug, T: ::std::fmt::Debug
{
let answer = match *elem {
ProjectionElem::Deref => {
let ty = self.to_ty(tcx)
.builtin_deref(true)
@ -94,8 +142,10 @@ impl<'a, 'gcx, 'tcx> PlaceTy<'tcx> {
bug!("cannot downcast non-ADT type: `{:?}`", self)
}
},
ProjectionElem::Field(_, fty) => PlaceTy::Ty { ty: fty }
}
ProjectionElem::Field(ref f, ref fty) => PlaceTy::Ty { ty: handle_field(&self, f, fty) }
};
debug!("projection_ty self: {:?} elem: {:?} yields: {:?}", self, elem, answer);
answer
}
}

View file

@ -797,8 +797,9 @@ macro_rules! make_mir_visitor {
) {
let UserTypeProjection {
ref $($mutability)* base,
projs: _, // Note: Does not visit projection elems!
} = *ty;
self.visit_user_type_annotation(base)
self.visit_user_type_annotation(base);
}
fn super_user_type_annotation(

View file

@ -168,7 +168,9 @@ impl Visitor<'tcx> for LocalAnalyzer<'mir, 'a, 'll, 'tcx> {
let base_ty = self.fx.monomorphize(&base_ty);
// ZSTs don't require any actual memory access.
let elem_ty = base_ty.projection_ty(cx.tcx, &proj.elem).to_ty(cx.tcx);
let elem_ty = base_ty
.projection_ty(cx.tcx, &proj.elem)
.to_ty(cx.tcx);
let elem_ty = self.fx.monomorphize(&elem_ty);
if cx.layout_of(elem_ty).is_zst() {
return;

View file

@ -517,7 +517,8 @@ impl FunctionCx<'a, 'll, 'tcx> {
let mut subslice = cg_base.project_index(bx,
C_usize(bx.cx, from as u64));
let projected_ty = PlaceTy::Ty { ty: cg_base.layout.ty }
.projection_ty(tcx, &projection.elem).to_ty(bx.tcx());
.projection_ty(tcx, &projection.elem)
.to_ty(bx.tcx());
subslice.layout = bx.cx.layout_of(self.monomorphize(&projected_ty));
if subslice.layout.is_unsized() {

View file

@ -284,7 +284,7 @@ impl<'a, 'b, 'gcx, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'gcx, 'tcx> {
if let Err(terr) = self.cx.relate_type_and_user_type(
constant.ty,
ty::Variance::Invariant,
&UserTypeProjection { base: user_ty },
&UserTypeProjection { base: user_ty, projs: vec![], },
location.to_locations(),
ConstraintCategory::Boring,
) {
@ -980,7 +980,6 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
a, v, user_ty, locations,
);
// FIXME
match user_ty.base {
UserTypeAnnotation::Ty(canonical_ty) => {
let (ty, _) = self.infcx
@ -991,6 +990,20 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
// ambient variance to get the right relationship.
let v1 = ty::Contravariant.xform(v);
let tcx = self.infcx.tcx;
let mut projected_ty = PlaceTy::from_ty(ty);
for proj in &user_ty.projs {
projected_ty = projected_ty.projection_ty_core(
tcx, proj, |this, field, &()| {
let ty = this.field_ty(tcx, field);
self.normalize(ty, locations)
});
}
debug!("user_ty base: {:?} freshened: {:?} projs: {:?} yields: {:?}",
user_ty.base, ty, user_ty.projs, projected_ty);
let ty = projected_ty.to_ty(tcx);
self.relate_types(ty, v1, a, locations, category)?;
}
UserTypeAnnotation::TypeOf(def_id, canonical_substs) => {
@ -1000,6 +1013,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
) = self.infcx
.instantiate_canonical_with_fresh_inference_vars(DUMMY_SP, &canonical_substs);
// FIXME: add user_ty.projs support to `AscribeUserType`.
self.fully_perform_op(
locations,
category,
@ -1173,7 +1187,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
if let Err(terr) = self.relate_type_and_user_type(
rv_ty,
ty::Variance::Invariant,
&UserTypeProjection { base: user_ty },
&UserTypeProjection { base: user_ty, projs: vec![], },
location.to_locations(),
ConstraintCategory::Boring,
) {

View file

@ -147,7 +147,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
kind: StatementKind::AscribeUserType(
place.clone(),
Variance::Invariant,
box UserTypeProjection { base: user_ty },
box UserTypeProjection { base: user_ty, projs: vec![], },
),
},
);
@ -167,7 +167,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
kind: StatementKind::AscribeUserType(
Place::Local(temp.clone()),
Variance::Invariant,
box UserTypeProjection { base: user_ty },
box UserTypeProjection { base: user_ty, projs: vec![], },
),
},
);

View file

@ -32,6 +32,8 @@ mod simplify;
mod test;
mod util;
use std::convert::TryFrom;
/// ArmHasGuard is isomorphic to a boolean flag. It indicates whether
/// a match arm has a guard expression attached to it.
#[derive(Copy, Clone, Debug)]
@ -541,11 +543,13 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
ref slice,
ref suffix,
} => {
let from = u32::try_from(prefix.len()).unwrap();
let to = u32::try_from(suffix.len()).unwrap();
for subpattern in prefix {
self.visit_bindings(subpattern, &pattern_user_ty.index(), f);
}
for subpattern in slice {
self.visit_bindings(subpattern, &pattern_user_ty.subslice(), f);
self.visit_bindings(subpattern, &pattern_user_ty.subslice(from, to), f);
}
for subpattern in suffix {
self.visit_bindings(subpattern, &pattern_user_ty.index(), f);
@ -555,25 +559,28 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
PatternKind::Deref { ref subpattern } => {
self.visit_bindings(subpattern, &pattern_user_ty.deref(), f);
}
PatternKind::AscribeUserType { ref subpattern, user_ty, user_ty_span } => {
PatternKind::AscribeUserType { ref subpattern, ref user_ty, user_ty_span } => {
// This corresponds to something like
//
// ```
// let A::<'a>(_): A<'static> = ...;
// ```
let pattern_user_ty = pattern_user_ty.add_user_type(user_ty, user_ty_span);
self.visit_bindings(subpattern, &pattern_user_ty, f)
let subpattern_user_ty = pattern_user_ty.add_user_type(user_ty, user_ty_span);
self.visit_bindings(subpattern, &subpattern_user_ty, f)
}
PatternKind::Leaf { ref subpatterns } => {
for (j, subpattern) in subpatterns.iter().enumerate() {
self.visit_bindings(&subpattern.pattern, &pattern_user_ty.leaf(j), f);
for subpattern in subpatterns {
let subpattern_user_ty = pattern_user_ty.leaf(subpattern.field);
self.visit_bindings(&subpattern.pattern, &subpattern_user_ty, f);
}
}
PatternKind::Variant { ref subpatterns, .. } => {
for (j, subpattern) in subpatterns.iter().enumerate() {
self.visit_bindings(&subpattern.pattern, &pattern_user_ty.variant(j), f);
PatternKind::Variant { adt_def, substs: _, variant_index, ref subpatterns } => {
for subpattern in subpatterns {
let subpattern_user_ty = pattern_user_ty.variant(
adt_def, variant_index, subpattern.field);
self.visit_bindings(&subpattern.pattern, &subpattern_user_ty, f);
}
}
}
@ -1329,7 +1336,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
kind: StatementKind::AscribeUserType(
ascription.source.clone(),
ty::Variance::Covariant,
box ascription.user_ty.user_ty(),
box ascription.user_ty.clone().user_ty(),
),
},
);

View file

@ -63,10 +63,10 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
candidate: &mut Candidate<'pat, 'tcx>)
-> Result<(), MatchPair<'pat, 'tcx>> {
match *match_pair.pattern.kind {
PatternKind::AscribeUserType { ref subpattern, user_ty, user_ty_span } => {
PatternKind::AscribeUserType { ref subpattern, ref user_ty, user_ty_span } => {
candidate.ascriptions.push(Ascription {
span: user_ty_span,
user_ty,
user_ty: user_ty.clone(),
source: match_pair.place.clone(),
});

View file

@ -21,7 +21,7 @@ use const_eval::{const_field, const_variant_index};
use hair::util::UserAnnotatedTyHelpers;
use rustc::mir::{fmt_const_val, Field, BorrowKind, Mutability};
use rustc::mir::{UserTypeAnnotation, UserTypeProjection, UserTypeProjections};
use rustc::mir::{ProjectionElem, UserTypeAnnotation, UserTypeProjection, UserTypeProjections};
use rustc::mir::interpret::{Scalar, GlobalId, ConstValue, sign_extend};
use rustc::ty::{self, Region, TyCtxt, AdtDef, Ty};
use rustc::ty::subst::{Substs, Kind};
@ -86,43 +86,87 @@ impl<'tcx> PatternTypeProjections<'tcx> {
PatternTypeProjections { contents: vec![] }
}
pub(crate) fn index(&self) -> Self {
unimplemented!()
fn map_projs(&self,
mut f: impl FnMut(&PatternTypeProjection<'tcx>) -> PatternTypeProjection<'tcx>)
-> Self
{
PatternTypeProjections {
contents: self.contents
.iter()
.map(|(proj, span)| (f(proj), *span))
.collect(), }
}
pub(crate) fn subslice(&self) -> Self {
unimplemented!()
pub(crate) fn index(&self) -> Self { self.map_projs(|pat_ty_proj| pat_ty_proj.index()) }
pub(crate) fn subslice(&self, from: u32, to: u32) -> Self {
self.map_projs(|pat_ty_proj| pat_ty_proj.subslice(from, to))
}
pub(crate) fn deref(&self) -> Self {
unimplemented!()
pub(crate) fn deref(&self) -> Self { self.map_projs(|pat_ty_proj| pat_ty_proj.deref()) }
pub(crate) fn leaf(&self, field: Field) -> Self {
self.map_projs(|pat_ty_proj| pat_ty_proj.leaf(field))
}
pub(crate) fn add_user_type(&self, user_ty: PatternTypeProjection<'tcx>, sp: Span) -> Self {
pub(crate) fn variant(&self,
adt_def: &'tcx AdtDef,
variant_index: usize,
field: Field) -> Self {
self.map_projs(|pat_ty_proj| pat_ty_proj.variant(adt_def, variant_index, field))
}
pub(crate) fn add_user_type(&self, user_ty: &PatternTypeProjection<'tcx>, sp: Span) -> Self {
let mut new = self.clone();
new.contents.push((user_ty, sp));
new.contents.push((user_ty.clone(), sp));
new
}
pub(crate) fn leaf(&self, _index: usize) -> Self {
unimplemented!()
}
pub(crate) fn variant(&self, _index: usize) -> Self {
unimplemented!()
}
}
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct PatternTypeProjection<'tcx>(UserTypeProjection<'tcx>);
impl<'tcx> PatternTypeProjection<'tcx> {
pub(crate) fn index(&self) -> Self {
let mut new = self.clone();
new.0.projs.push(ProjectionElem::Index(()));
new
}
pub(crate) fn subslice(&self, from: u32, to: u32) -> Self {
let mut new = self.clone();
new.0.projs.push(ProjectionElem::Subslice { from, to });
new
}
pub(crate) fn deref(&self) -> Self {
let mut new = self.clone();
new.0.projs.push(ProjectionElem::Deref);
new
}
pub(crate) fn leaf(&self, field: Field) -> Self {
let mut new = self.clone();
new.0.projs.push(ProjectionElem::Field(field, ()));
new
}
pub(crate) fn variant(&self,
adt_def: &'tcx AdtDef,
variant_index: usize,
field: Field) -> Self {
let mut new = self.clone();
new.0.projs.push(ProjectionElem::Downcast(adt_def, variant_index));
new.0.projs.push(ProjectionElem::Field(field, ()));
new
}
pub(crate) fn from_canonical_ty(c_ty: ty::CanonicalTy<'tcx>) -> Self {
Self::from_user_type(UserTypeAnnotation::Ty(c_ty))
}
pub(crate) fn from_user_type(u_ty: UserTypeAnnotation<'tcx>) -> Self {
Self::from_user_type_proj(UserTypeProjection { base: u_ty })
Self::from_user_type_proj(UserTypeProjection { base: u_ty, projs: vec![], })
}
pub(crate) fn from_user_type_proj(u_ty: UserTypeProjection<'tcx>) -> Self {
@ -1086,7 +1130,7 @@ impl<'tcx> PatternFoldable<'tcx> for PatternKind<'tcx> {
PatternKind::Wild => PatternKind::Wild,
PatternKind::AscribeUserType {
ref subpattern,
user_ty,
ref user_ty,
user_ty_span,
} => PatternKind::AscribeUserType {
subpattern: subpattern.fold_with(folder),

View file

@ -9,11 +9,11 @@ fn variable_no_initializer() {
}
fn tuple_no_initializer() {
// FIXME(#47187): We are not propagating ascribed type through tuples.
let x = 22;
let (y, z): (&'static u32, &'static u32);
y = &x;
y = &x; //~ ERROR
}
fn ref_with_ascribed_static_type() -> u32 {
@ -34,11 +34,11 @@ fn ref_with_ascribed_any_type() -> u32 {
struct Single<T> { value: T }
fn struct_no_initializer() {
// FIXME(#47187): We are not propagating ascribed type through patterns.
let x = 22;
let Single { value: y }: Single<&'static u32>;
y = &x;
y = &x; //~ ERROR
}
fn variable_with_initializer() {
@ -91,26 +91,26 @@ fn struct_double_field_underscore_with_initializer() {
}
fn static_to_a_to_static_through_variable<'a>(x: &'a u32) -> &'static u32 {
// The error in this test is inconsistency with
// `static_to_a_to_static_through_tuple`, but "feels right" to
// me. It occurs because we special case the single binding case
// and force the type of `y` to be `&'a u32`, even though the
// right-hand side has type `&'static u32`.
let y: &'a u32 = &22;
y //~ ERROR
}
fn static_to_a_to_static_through_tuple<'a>(x: &'a u32) -> &'static u32 {
// FIXME(#47187): The fact that this type-checks is perhaps surprising.
// What happens is that the right-hand side is constrained to have
// type `&'a u32`, which is possible, because it has type
// `&'static u32`. The variable `y` is then forced to have type
// `&'static u32`, but it is constrained only by the right-hand
// side, not the ascribed type, and hence it passes.
let (y, _z): (&'a u32, u32) = (&22, 44);
y
y //~ ERROR
}
fn a_to_static_then_static<'a>(x: &'a u32) -> &'static u32 {

View file

@ -8,6 +8,16 @@ LL | y = &x; //~ ERROR
LL | }
| - `x` dropped here while still borrowed
error[E0597]: `x` does not live long enough
--> $DIR/patterns.rs:16:9
|
LL | let (y, z): (&'static u32, &'static u32);
| ---------------------------- type annotation requires that `x` is borrowed for `'static`
LL | y = &x; //~ ERROR
| ^^ borrowed value does not live long enough
LL | }
| - `x` dropped here while still borrowed
error[E0597]: `x` does not live long enough
--> $DIR/patterns.rs:22:13
|
@ -19,6 +29,16 @@ LL | **z
LL | }
| - `x` dropped here while still borrowed
error[E0597]: `x` does not live long enough
--> $DIR/patterns.rs:41:9
|
LL | let Single { value: y }: Single<&'static u32>;
| -------------------- type annotation requires that `x` is borrowed for `'static`
LL | y = &x; //~ ERROR
| ^^ borrowed value does not live long enough
LL | }
| - `x` dropped here while still borrowed
error[E0597]: `x` does not live long enough
--> $DIR/patterns.rs:46:27
|
@ -127,6 +147,15 @@ LL | fn static_to_a_to_static_through_variable<'a>(x: &'a u32) -> &'static u32 {
LL | y //~ ERROR
| ^ returning this value requires that `'a` must outlive `'static`
error: unsatisfied lifetime constraints
--> $DIR/patterns.rs:113:5
|
LL | fn static_to_a_to_static_through_tuple<'a>(x: &'a u32) -> &'static u32 {
| -- lifetime `'a` defined here
...
LL | y //~ ERROR
| ^ returning this value requires that `'a` must outlive `'static`
error: unsatisfied lifetime constraints
--> $DIR/patterns.rs:117:18
|
@ -135,7 +164,7 @@ LL | fn a_to_static_then_static<'a>(x: &'a u32) -> &'static u32 {
LL | let (y, _z): (&'static u32, u32) = (x, 44); //~ ERROR
| ^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
error: aborting due to 14 previous errors
error: aborting due to 17 previous errors
Some errors occurred: E0597, E0716.
For more information about an error, try `rustc --explain E0597`.