Skip to content

Commit b15cb29

Browse files
committed
Refactor variance diagnostics to work with more types
Instead of special-casing mutable pointers/references, we now support general generic types (currently, we handle `ty::Ref`, `ty::RawPtr`, and `ty::Adt`) When a `ty::Adt` is involved, we show an additional note explaining which of the type's generic parameters is invariant (e.g. the `T` in `Cell<T>`). Currently, we don't explain *why* a particular generic parameter ends up becoming invariant. In the general case, this could require printing a long 'backtrace' of types, so doing this would be more suitable for a follow-up PR. We still only handle the case where our variance switches to `ty::Invariant`.
1 parent 78fd0f6 commit b15cb29

25 files changed

+247
-40
lines changed

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

+37-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_infer::infer::{
66
error_reporting::unexpected_hidden_region_diagnostic, NllRegionVariableOrigin,
77
};
88
use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
9-
use rustc_middle::ty::subst::Subst;
9+
use rustc_middle::ty::subst::{InternalSubsts, Subst};
1010
use rustc_middle::ty::{self, RegionVid, Ty};
1111
use rustc_span::symbol::{kw, sym};
1212
use rustc_span::{BytePos, Span};
@@ -334,13 +334,43 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
334334

335335
match variance_info {
336336
ty::VarianceDiagInfo::None => {}
337-
ty::VarianceDiagInfo::Mut { kind, ty } => {
338-
let kind_name = match kind {
339-
ty::VarianceDiagMutKind::Ref => "reference",
340-
ty::VarianceDiagMutKind::RawPtr => "pointer",
337+
ty::VarianceDiagInfo::Invariant { ty, param_index } => {
338+
let (desc, note) = match ty.kind() {
339+
ty::RawPtr(ty_mut) => {
340+
assert_eq!(ty_mut.mutbl, rustc_hir::Mutability::Mut);
341+
(
342+
format!("a mutable pointer to {}", ty_mut.ty),
343+
"mutable pointers are invariant over their type parameter".to_string(),
344+
)
345+
}
346+
ty::Ref(_, inner_ty, mutbl) => {
347+
assert_eq!(*mutbl, rustc_hir::Mutability::Mut);
348+
(
349+
format!("a mutable reference to {}", inner_ty),
350+
"mutable references are invariant over their type parameter"
351+
.to_string(),
352+
)
353+
}
354+
ty::Adt(adt, substs) => {
355+
let generic_arg = substs[param_index as usize];
356+
let identity_substs =
357+
InternalSubsts::identity_for_item(self.infcx.tcx, adt.did);
358+
let base_ty = self.infcx.tcx.mk_adt(adt, identity_substs);
359+
let base_generic_arg = identity_substs[param_index as usize];
360+
let adt_desc = adt.descr();
361+
362+
let desc = format!(
363+
"the type {ty}, which makes the generic argument {generic_arg} invariant"
364+
);
365+
let note = format!(
366+
"the {adt_desc} {base_ty} is invariant over the parameter {base_generic_arg}"
367+
);
368+
(desc, note)
369+
}
370+
_ => panic!("Unexpected type {:?}", ty),
341371
};
342-
diag.note(&format!("requirement occurs because of a mutable {kind_name} to {ty}",));
343-
diag.note(&format!("mutable {kind_name}s are invariant over their type parameter"));
372+
diag.note(&format!("requirement occurs because of {desc}",));
373+
diag.note(&note);
344374
diag.help("see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance");
345375
}
346376
}

compiler/rustc_infer/src/infer/combine.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,9 @@ impl<'tcx> TypeRelation<'tcx> for Generalizer<'_, 'tcx> {
572572
// (e.g., #41849).
573573
relate::relate_substs(self, None, a_subst, b_subst)
574574
} else {
575-
let opt_variances = self.tcx().variances_of(item_def_id);
576-
relate::relate_substs(self, Some(&opt_variances), a_subst, b_subst)
575+
let tcx = self.tcx();
576+
let opt_variances = tcx.variances_of(item_def_id);
577+
relate::relate_substs(self, Some((item_def_id, &opt_variances)), a_subst, b_subst)
577578
}
578579
}
579580

compiler/rustc_middle/src/ty/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub use self::sty::{
7777
GeneratorSubsts, GeneratorSubstsParts, InlineConstSubsts, InlineConstSubstsParts, ParamConst,
7878
ParamTy, PolyExistentialProjection, PolyExistentialTraitRef, PolyFnSig, PolyGenSig,
7979
PolyTraitRef, ProjectionTy, Region, RegionKind, RegionVid, TraitRef, TyKind, TypeAndMut,
80-
UpvarSubsts, VarianceDiagInfo, VarianceDiagMutKind,
80+
UpvarSubsts, VarianceDiagInfo,
8181
};
8282
pub use self::trait_def::TraitDef;
8383

compiler/rustc_middle/src/ty/relate.rs

+27-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
use crate::mir::interpret::{get_slice_bytes, ConstValue, GlobalAlloc, Scalar};
88
use crate::ty::error::{ExpectedFound, TypeError};
9-
use crate::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
9+
use crate::ty::subst::{GenericArg, GenericArgKind, Subst, SubstsRef};
1010
use crate::ty::{self, Ty, TyCtxt, TypeFoldable};
1111
use rustc_hir as ast;
1212
use rustc_hir::def_id::DefId;
@@ -59,8 +59,9 @@ pub trait TypeRelation<'tcx>: Sized {
5959
item_def_id, a_subst, b_subst
6060
);
6161

62-
let opt_variances = self.tcx().variances_of(item_def_id);
63-
relate_substs(self, Some(opt_variances), a_subst, b_subst)
62+
let tcx = self.tcx();
63+
let opt_variances = tcx.variances_of(item_def_id);
64+
relate_substs(self, Some((item_def_id, opt_variances)), a_subst, b_subst)
6465
}
6566

6667
/// Switch variance for the purpose of relating `a` and `b`.
@@ -116,7 +117,7 @@ pub fn relate_type_and_mut<'tcx, R: TypeRelation<'tcx>>(
116117
relation: &mut R,
117118
a: ty::TypeAndMut<'tcx>,
118119
b: ty::TypeAndMut<'tcx>,
119-
kind: ty::VarianceDiagMutKind,
120+
base_ty: Ty<'tcx>,
120121
) -> RelateResult<'tcx, ty::TypeAndMut<'tcx>> {
121122
debug!("{}.mts({:?}, {:?})", relation.tag(), a, b);
122123
if a.mutbl != b.mutbl {
@@ -125,7 +126,9 @@ pub fn relate_type_and_mut<'tcx, R: TypeRelation<'tcx>>(
125126
let mutbl = a.mutbl;
126127
let (variance, info) = match mutbl {
127128
ast::Mutability::Not => (ty::Covariant, ty::VarianceDiagInfo::None),
128-
ast::Mutability::Mut => (ty::Invariant, ty::VarianceDiagInfo::Mut { kind, ty: a.ty }),
129+
ast::Mutability::Mut => {
130+
(ty::Invariant, ty::VarianceDiagInfo::Invariant { ty: base_ty, param_index: 0 })
131+
}
129132
};
130133
let ty = relation.relate_with_variance(variance, info, a.ty, b.ty)?;
131134
Ok(ty::TypeAndMut { ty, mutbl })
@@ -134,15 +137,29 @@ pub fn relate_type_and_mut<'tcx, R: TypeRelation<'tcx>>(
134137

135138
pub fn relate_substs<'tcx, R: TypeRelation<'tcx>>(
136139
relation: &mut R,
137-
variances: Option<&[ty::Variance]>,
140+
variances: Option<(DefId, &[ty::Variance])>,
138141
a_subst: SubstsRef<'tcx>,
139142
b_subst: SubstsRef<'tcx>,
140143
) -> RelateResult<'tcx, SubstsRef<'tcx>> {
141144
let tcx = relation.tcx();
145+
let mut cached_ty = None;
142146

143147
let params = iter::zip(a_subst, b_subst).enumerate().map(|(i, (a, b))| {
144-
let variance = variances.map_or(ty::Invariant, |v| v[i]);
145-
relation.relate_with_variance(variance, ty::VarianceDiagInfo::default(), a, b)
148+
let (variance, variance_info) = match variances {
149+
Some((ty_def_id, variances)) => {
150+
let variance = variances[i];
151+
let variance_info = if variance == ty::Invariant {
152+
let ty =
153+
cached_ty.get_or_insert_with(|| tcx.type_of(ty_def_id).subst(tcx, a_subst));
154+
ty::VarianceDiagInfo::Invariant { ty, param_index: i.try_into().unwrap() }
155+
} else {
156+
ty::VarianceDiagInfo::default()
157+
};
158+
(variance, variance_info)
159+
}
160+
None => (ty::Invariant, ty::VarianceDiagInfo::default()),
161+
};
162+
relation.relate_with_variance(variance, variance_info, a, b)
146163
});
147164

148165
tcx.mk_substs(params)
@@ -436,7 +453,7 @@ pub fn super_relate_tys<'tcx, R: TypeRelation<'tcx>>(
436453
}
437454

438455
(&ty::RawPtr(a_mt), &ty::RawPtr(b_mt)) => {
439-
let mt = relate_type_and_mut(relation, a_mt, b_mt, ty::VarianceDiagMutKind::RawPtr)?;
456+
let mt = relate_type_and_mut(relation, a_mt, b_mt, a)?;
440457
Ok(tcx.mk_ptr(mt))
441458
}
442459

@@ -449,7 +466,7 @@ pub fn super_relate_tys<'tcx, R: TypeRelation<'tcx>>(
449466
)?;
450467
let a_mt = ty::TypeAndMut { ty: a_ty, mutbl: a_mutbl };
451468
let b_mt = ty::TypeAndMut { ty: b_ty, mutbl: b_mutbl };
452-
let mt = relate_type_and_mut(relation, a_mt, b_mt, ty::VarianceDiagMutKind::Ref)?;
469+
let mt = relate_type_and_mut(relation, a_mt, b_mt, a)?;
453470
Ok(tcx.mk_ref(r, mt))
454471
}
455472

compiler/rustc_middle/src/ty/sty.rs

+10-20
Original file line numberDiff line numberDiff line change
@@ -2282,36 +2282,26 @@ pub enum VarianceDiagInfo<'tcx> {
22822282
/// We will not add any additional information to error messages.
22832283
#[default]
22842284
None,
2285-
/// We switched our variance because a type occurs inside
2286-
/// the generic argument of a mutable reference or pointer
2287-
/// (`*mut T` or `&mut T`). In either case, our variance
2288-
/// will always be `Invariant`.
2289-
Mut {
2290-
/// Tracks whether we had a mutable pointer or reference,
2291-
/// for better error messages
2292-
kind: VarianceDiagMutKind,
2293-
/// The type parameter of the mutable pointer/reference
2294-
/// (the `T` in `&mut T` or `*mut T`).
2285+
/// We switched our variance because a generic argument occurs inside
2286+
/// the invariant generic argument of another type.
2287+
Invariant {
2288+
/// The generic type containing the generic parameter
2289+
/// that changes the variance (e.g. `*mut T`, `MyStruct<T>`)
22952290
ty: Ty<'tcx>,
2291+
/// The index of the generic parameter being used
2292+
/// (e.g. `0` for `*mut T`, `1` for `MyStruct<'CovariantParam, 'InvariantParam>`)
2293+
param_index: u32,
22962294
},
22972295
}
22982296

2299-
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
2300-
pub enum VarianceDiagMutKind {
2301-
/// A mutable raw pointer (`*mut T`)
2302-
RawPtr,
2303-
/// A mutable reference (`&mut T`)
2304-
Ref,
2305-
}
2306-
23072297
impl<'tcx> VarianceDiagInfo<'tcx> {
23082298
/// Mirrors `Variance::xform` - used to 'combine' the existing
23092299
/// and new `VarianceDiagInfo`s when our variance changes.
23102300
pub fn xform(self, other: VarianceDiagInfo<'tcx>) -> VarianceDiagInfo<'tcx> {
2311-
// For now, just use the first `VarianceDiagInfo::Mut` that we see
2301+
// For now, just use the first `VarianceDiagInfo::Invariant` that we see
23122302
match self {
23132303
VarianceDiagInfo::None => other,
2314-
VarianceDiagInfo::Mut { .. } => self,
2304+
VarianceDiagInfo::Invariant { .. } => self,
23152305
}
23162306
}
23172307
}

src/test/ui/associated-types/cache/project-fn-ret-invariant.krisskross.nll.stderr

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ LL | (a, b)
1010
| ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
1111
|
1212
= help: consider adding the following bound: `'a: 'b`
13+
= note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
14+
= note: the struct Type<'a> is invariant over the parameter 'a
15+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
1316

1417
error: lifetime may not live long enough
1518
--> $DIR/project-fn-ret-invariant.rs:56:5
@@ -23,6 +26,9 @@ LL | (a, b)
2326
| ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
2427
|
2528
= help: consider adding the following bound: `'b: 'a`
29+
= note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
30+
= note: the struct Type<'a> is invariant over the parameter 'a
31+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
2632

2733
help: `'a` and `'b` must be the same: replace one with the other
2834

src/test/ui/associated-types/cache/project-fn-ret-invariant.oneuse.nll.stderr

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ LL | let a = bar(f, x);
1010
| ^^^^^^^^^ argument requires that `'a` must outlive `'b`
1111
|
1212
= help: consider adding the following bound: `'a: 'b`
13+
= note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
14+
= note: the struct Type<'a> is invariant over the parameter 'a
15+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
1316

1417
error: lifetime may not live long enough
1518
--> $DIR/project-fn-ret-invariant.rs:40:13
@@ -23,6 +26,9 @@ LL | let b = bar(f, y);
2326
| ^^^^^^^^^ argument requires that `'b` must outlive `'a`
2427
|
2528
= help: consider adding the following bound: `'b: 'a`
29+
= note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
30+
= note: the struct Type<'a> is invariant over the parameter 'a
31+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
2632

2733
help: `'a` and `'b` must be the same: replace one with the other
2834

src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.nll.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ LL | fn baz<'a, 'b>(x: Type<'a>) -> Type<'static> {
66
...
77
LL | bar(foo, x)
88
| ^^^^^^^^^^^ returning this value requires that `'a` must outlive `'static`
9+
|
10+
= note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
11+
= note: the struct Type<'a> is invariant over the parameter 'a
12+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
913

1014
error: aborting due to previous error
1115

src/test/ui/c-variadic/variadic-ffi-4.stderr

+28
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f
77
| lifetime `'f` defined here
88
LL | ap
99
| ^^ function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'f`
10+
|
11+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
12+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
13+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
1014

1115
error: lifetime may not live long enough
1216
--> $DIR/variadic-ffi-4.rs:8:5
@@ -17,6 +21,10 @@ LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f
1721
| lifetime `'f` defined here
1822
LL | ap
1923
| ^^ returning this value requires that `'1` must outlive `'f`
24+
|
25+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
26+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
27+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
2028

2129
error: lifetime may not live long enough
2230
--> $DIR/variadic-ffi-4.rs:14:5
@@ -25,6 +33,10 @@ LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaListImpl<'stati
2533
| -- has type `VaListImpl<'1>`
2634
LL | ap
2735
| ^^ returning this value requires that `'1` must outlive `'static`
36+
|
37+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
38+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
39+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
2840

2941
error: lifetime may not live long enough
3042
--> $DIR/variadic-ffi-4.rs:18:31
@@ -44,6 +56,10 @@ LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut
4456
| has type `&mut VaListImpl<'1>`
4557
LL | *ap0 = ap1;
4658
| ^^^^ assignment requires that `'1` must outlive `'2`
59+
|
60+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
61+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
62+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
4763

4864
error: lifetime may not live long enough
4965
--> $DIR/variadic-ffi-4.rs:22:5
@@ -54,6 +70,10 @@ LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut
5470
| has type `&mut VaListImpl<'1>`
5571
LL | *ap0 = ap1;
5672
| ^^^^ assignment requires that `'2` must outlive `'1`
73+
|
74+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
75+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
76+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
5777

5878
error: lifetime may not live long enough
5979
--> $DIR/variadic-ffi-4.rs:28:5
@@ -106,6 +126,10 @@ LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut
106126
| has type `&mut VaListImpl<'1>`
107127
LL | *ap0 = ap1.clone();
108128
| ^^^^^^^^^^^ argument requires that `'1` must outlive `'2`
129+
|
130+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
131+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
132+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
109133

110134
error: lifetime may not live long enough
111135
--> $DIR/variadic-ffi-4.rs:35:12
@@ -116,6 +140,10 @@ LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut
116140
| has type `&mut VaListImpl<'1>`
117141
LL | *ap0 = ap1.clone();
118142
| ^^^^^^^^^^^ argument requires that `'2` must outlive `'1`
143+
|
144+
= note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
145+
= note: the struct VaListImpl<'f> is invariant over the parameter 'f
146+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
119147

120148
error: aborting due to 11 previous errors
121149

src/test/ui/hr-subtype/hr-subtype.free_inv_x_vs_free_inv_y.nll.stderr

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ LL | | fn(Inv<'y>)) }
1313
| |______________- in this macro invocation
1414
|
1515
= help: consider adding the following bound: `'x: 'y`
16+
= note: requirement occurs because of the type Inv<'_>, which makes the generic argument '_ invariant
17+
= note: the struct Inv<'a> is invariant over the parameter 'a
18+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
1619
= note: this error originates in the macro `check` (in Nightly builds, run with -Z macro-backtrace for more info)
1720

1821
error: lifetime may not live long enough
@@ -30,6 +33,9 @@ LL | | fn(Inv<'y>)) }
3033
| |______________- in this macro invocation
3134
|
3235
= help: consider adding the following bound: `'x: 'y`
36+
= note: requirement occurs because of the type Inv<'_>, which makes the generic argument '_ invariant
37+
= note: the struct Inv<'a> is invariant over the parameter 'a
38+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
3339
= note: this error originates in the macro `check` (in Nightly builds, run with -Z macro-backtrace for more info)
3440

3541
error: aborting due to 2 previous errors

src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ LL | | });
5151
| | |
5252
| |______`cell_a` escapes the function body here
5353
| argument requires that `'a` must outlive `'static`
54+
|
55+
= note: requirement occurs because of the type Cell<&'_#10r u32>, which makes the generic argument &'_#10r u32 invariant
56+
= note: the struct Cell<T> is invariant over the parameter T
57+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
5458

5559
error: aborting due to previous error
5660

src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ LL | | });
5151
| | |
5252
| |______`cell_a` escapes the function body here
5353
| argument requires that `'a` must outlive `'static`
54+
|
55+
= note: requirement occurs because of the type Cell<&'_#11r u32>, which makes the generic argument &'_#11r u32 invariant
56+
= note: the struct Cell<T> is invariant over the parameter T
57+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
5458

5559
error: aborting due to previous error
5660

src/test/ui/nll/where_clauses_in_structs.stderr

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ LL | Foo { x, y };
99
| ^ this usage requires that `'a` must outlive `'b`
1010
|
1111
= help: consider adding the following bound: `'a: 'b`
12+
= note: requirement occurs because of the type Cell<&u32>, which makes the generic argument &u32 invariant
13+
= note: the struct Cell<T> is invariant over the parameter T
14+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
1215

1316
error: aborting due to previous error
1417

0 commit comments

Comments
 (0)