Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c2c6400

Browse files
committedOct 31, 2023
finish :>
1 parent 12c8a39 commit c2c6400

21 files changed

+182
-36
lines changed
 

‎compiler/rustc_middle/src/ty/sty.rs

+22
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,28 @@ impl<'tcx> AliasTy<'tcx> {
12451245
}
12461246
}
12471247

1248+
/// Whether this alias type is an opaque.
1249+
pub fn is_opaque(self, tcx: TyCtxt<'tcx>) -> bool {
1250+
matches!(self.opt_kind(tcx), Some(ty::AliasKind::Opaque))
1251+
}
1252+
1253+
/// FIXME: rename `AliasTy` to `AliasTerm` and always handle
1254+
/// constants. This function can then be removed.
1255+
pub fn opt_kind(self, tcx: TyCtxt<'tcx>) -> Option<ty::AliasKind> {
1256+
match tcx.def_kind(self.def_id) {
1257+
DefKind::AssocTy
1258+
if let DefKind::Impl { of_trait: false } =
1259+
tcx.def_kind(tcx.parent(self.def_id)) =>
1260+
{
1261+
Some(ty::Inherent)
1262+
}
1263+
DefKind::AssocTy => Some(ty::Projection),
1264+
DefKind::OpaqueTy => Some(ty::Opaque),
1265+
DefKind::TyAlias => Some(ty::Weak),
1266+
_ => None,
1267+
}
1268+
}
1269+
12481270
pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
12491271
Ty::new_alias(tcx, self.kind(tcx), self)
12501272
}

‎compiler/rustc_trait_selection/src/solve/alias_relate.rs

+64-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! may apply, then we can compute the "intersection" of both normalizes-to by
1313
//! performing them together. This is used specifically to resolve ambiguities.
1414
use super::EvalCtxt;
15+
use rustc_infer::infer::DefineOpaqueTypes;
1516
use rustc_infer::traits::query::NoSolution;
1617
use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
1718
use rustc_middle::ty;
@@ -44,38 +45,46 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
4445
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
4546
}
4647

47-
(Some(_), None) => {
48+
(Some(alias), None) => {
4849
if rhs.is_infer() {
4950
self.relate(param_env, lhs, variance, rhs)?;
5051
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
52+
} else if alias.is_opaque(tcx) {
53+
self.define_opaque(param_env, alias, rhs)
5154
} else {
5255
Err(NoSolution)
5356
}
5457
}
55-
(None, Some(_)) => {
58+
(None, Some(alias)) => {
5659
if lhs.is_infer() {
5760
self.relate(param_env, lhs, variance, rhs)?;
5861
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
62+
} else if alias.is_opaque(tcx) {
63+
self.define_opaque(param_env, alias, lhs)
5964
} else {
6065
Err(NoSolution)
6166
}
6267
}
6368

6469
(Some(alias_lhs), Some(alias_rhs)) => {
65-
self.relate(param_env, alias_lhs, variance, alias_rhs)?;
66-
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
70+
self.relate_rigid_alias_or_opaque(param_env, alias_lhs, variance, alias_rhs)
6771
}
6872
}
6973
}
7074

71-
/// Normalize the `term` to equate it later.
75+
/// Normalize the `term` to equate it later. This does not define opaque types.
7276
fn try_normalize_term(
7377
&mut self,
7478
param_env: ty::ParamEnv<'tcx>,
7579
term: ty::Term<'tcx>,
7680
) -> Result<Option<ty::Term<'tcx>>, NoSolution> {
7781
match term.unpack() {
78-
ty::TermKind::Ty(ty) => Ok(self.try_normalize_ty(param_env, ty).map(Into::into)),
82+
ty::TermKind::Ty(ty) => {
83+
// We do no define opaque types here but instead do so in `relate_rigid_alias_or_opaque`.
84+
Ok(self
85+
.try_normalize_ty_recur(param_env, DefineOpaqueTypes::No, 0, ty)
86+
.map(Into::into))
87+
}
7988
ty::TermKind::Const(_) => {
8089
if let Some(alias) = term.to_alias_ty(self.tcx()) {
8190
let term = self.next_term_infer_of_kind(term);
@@ -92,4 +101,53 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
92101
}
93102
}
94103
}
104+
105+
fn define_opaque(
106+
&mut self,
107+
param_env: ty::ParamEnv<'tcx>,
108+
opaque: ty::AliasTy<'tcx>,
109+
term: ty::Term<'tcx>,
110+
) -> QueryResult<'tcx> {
111+
self.add_goal(Goal::new(
112+
self.tcx(),
113+
param_env,
114+
ty::ProjectionPredicate { projection_ty: opaque, term },
115+
));
116+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
117+
}
118+
119+
fn relate_rigid_alias_or_opaque(
120+
&mut self,
121+
param_env: ty::ParamEnv<'tcx>,
122+
lhs: ty::AliasTy<'tcx>,
123+
variance: ty::Variance,
124+
rhs: ty::AliasTy<'tcx>,
125+
) -> QueryResult<'tcx> {
126+
let tcx = self.tcx();
127+
let mut candidates = vec![];
128+
if lhs.is_opaque(tcx) {
129+
candidates.extend(
130+
self.probe_misc_candidate("define-lhs-opaque")
131+
.enter(|ecx| ecx.define_opaque(param_env, lhs, rhs.to_ty(tcx).into())),
132+
);
133+
}
134+
135+
if rhs.is_opaque(tcx) {
136+
candidates.extend(
137+
self.probe_misc_candidate("define-rhs-opaque")
138+
.enter(|ecx| ecx.define_opaque(param_env, rhs, lhs.to_ty(tcx).into())),
139+
);
140+
}
141+
142+
candidates.extend(self.probe_misc_candidate("args-relate").enter(|ecx| {
143+
ecx.relate(param_env, lhs, variance, rhs)?;
144+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
145+
}));
146+
147+
if let Some(result) = self.try_merge_responses(&candidates) {
148+
Ok(result)
149+
} else {
150+
self.flounder(&candidates)
151+
}
152+
}
95153
}

‎compiler/rustc_trait_selection/src/solve/inspect/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
467467
/// of the probe into the parent.
468468
pub fn integrate_snapshot(&mut self, probe: ProofTreeBuilder<'tcx>) {
469469
if let Some(this) = self.as_mut() {
470-
match (this, probe.state.unwrap().tree) {
470+
match (this, *probe.state.unwrap()) {
471471
(
472472
DebugSolver::Probe(WipProbe { steps, .. })
473473
| DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {

‎compiler/rustc_trait_selection/src/solve/mod.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
//! about it on zulip.
1717
use rustc_hir::def_id::DefId;
1818
use rustc_infer::infer::canonical::{Canonical, CanonicalVarValues};
19+
use rustc_infer::infer::DefineOpaqueTypes;
1920
use rustc_infer::traits::query::NoSolution;
2021
use rustc_middle::infer::canonical::CanonicalVarInfos;
2122
use rustc_middle::traits::solve::{
2223
CanonicalResponse, Certainty, ExternalConstraintsData, Goal, IsNormalizesToHack, QueryResult,
2324
Response,
2425
};
25-
use rustc_middle::ty::{self, Ty, TyCtxt, UniverseIndex};
26+
use rustc_middle::ty::{self, OpaqueTypeKey, Ty, TyCtxt, UniverseIndex};
2627
use rustc_middle::ty::{
2728
CoercePredicate, RegionOutlivesPredicate, SubtypePredicate, TypeOutlivesPredicate,
2829
};
@@ -299,23 +300,40 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
299300
param_env: ty::ParamEnv<'tcx>,
300301
ty: Ty<'tcx>,
301302
) -> Option<Ty<'tcx>> {
302-
self.try_normalize_ty_recur(param_env, 0, ty)
303+
self.try_normalize_ty_recur(param_env, DefineOpaqueTypes::Yes, 0, ty)
303304
}
304305

305306
fn try_normalize_ty_recur(
306307
&mut self,
307308
param_env: ty::ParamEnv<'tcx>,
309+
define_opaque_types: DefineOpaqueTypes,
308310
depth: usize,
309311
ty: Ty<'tcx>,
310312
) -> Option<Ty<'tcx>> {
311313
if depth >= self.local_overflow_limit() {
312314
return None;
313315
}
314316

315-
let ty::Alias(_, projection_ty) = *ty.kind() else {
317+
let ty::Alias(kind, projection_ty) = *ty.kind() else {
316318
return Some(ty);
317319
};
318320

321+
// We do no always define opaque types eagerly to allow non-defining uses in the defining scope.
322+
if let (DefineOpaqueTypes::No, ty::AliasKind::Opaque) = (define_opaque_types, kind) {
323+
if let Some(def_id) = projection_ty.def_id.as_local() {
324+
if self
325+
.unify_existing_opaque_tys(
326+
param_env,
327+
OpaqueTypeKey { def_id, args: projection_ty.args },
328+
self.next_ty_infer(),
329+
)
330+
.is_empty()
331+
{
332+
return Some(ty);
333+
}
334+
}
335+
}
336+
319337
match self.commit_if_ok(|this| {
320338
let normalized_ty = this.next_ty_infer();
321339
let normalizes_to_goal = Goal::new(
@@ -326,7 +344,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
326344
this.add_goal(normalizes_to_goal);
327345
this.try_evaluate_added_goals()?;
328346
let ty = this.resolve_vars_if_possible(normalized_ty);
329-
Ok(this.try_normalize_ty_recur(param_env, depth + 1, ty))
347+
Ok(this.try_normalize_ty_recur(param_env, define_opaque_types, depth + 1, ty))
330348
}) {
331349
Ok(ty) => ty,
332350
Err(NoSolution) => Some(ty),

‎compiler/rustc_trait_selection/src/solve/project_goals/opaques.rs

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
4444
// Prefer opaques registered already.
4545
let opaque_type_key =
4646
ty::OpaqueTypeKey { def_id: opaque_ty_def_id, args: opaque_ty.args };
47+
// FIXME: This also unifies the previous hidden type with the expected.
48+
//
49+
// If that fails, we insert `expected` as a new hidden type instead of
50+
// eagerly emitting an error.
4751
let matches =
4852
self.unify_existing_opaque_tys(goal.param_env, opaque_type_key, expected);
4953
if !matches.is_empty() {

‎tests/ui/impl-trait/recursive-coroutine.stderr ‎tests/ui/impl-trait/recursive-coroutine.current.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0720]: cannot resolve opaque type
2-
--> $DIR/recursive-coroutine.rs:5:13
2+
--> $DIR/recursive-coroutine.rs:7:13
33
|
44
LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0720]: cannot resolve opaque type
2+
--> $DIR/recursive-coroutine.rs:7:13
3+
|
4+
LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
6+
...
7+
LL | let mut gen = Box::pin(foo());
8+
| ------- coroutine captures itself here
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0720`.

‎tests/ui/impl-trait/recursive-coroutine.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// revisions: current next
2+
//[next] compile-flags: -Ztrait-solver=next
13
#![feature(coroutines, coroutine_trait)]
24

35
use std::ops::{Coroutine, CoroutineState};

‎tests/ui/impl-trait/two_tait_defining_each_other.stderr ‎tests/ui/impl-trait/two_tait_defining_each_other.current.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
error: opaque type's hidden type cannot be another opaque type from the same scope
2-
--> $DIR/two_tait_defining_each_other.rs:12:5
2+
--> $DIR/two_tait_defining_each_other.rs:16:5
33
|
44
LL | x // A's hidden type is `Bar`, because all the hidden types of `B` are compared with each other
55
| ^ one of the two opaque types used here has to be outside its defining scope
66
|
77
note: opaque type whose hidden type is being assigned
8-
--> $DIR/two_tait_defining_each_other.rs:4:10
8+
--> $DIR/two_tait_defining_each_other.rs:8:10
99
|
1010
LL | type B = impl Foo;
1111
| ^^^^^^^^
1212
note: opaque type being used as hidden type
13-
--> $DIR/two_tait_defining_each_other.rs:3:10
13+
--> $DIR/two_tait_defining_each_other.rs:7:10
1414
|
1515
LL | type A = impl Foo;
1616
| ^^^^^^^^

‎tests/ui/impl-trait/two_tait_defining_each_other.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// revisions: current next
2+
//[next] compile-flags: -Ztrait-solver=next
3+
//[next] check-pass
4+
15
#![feature(type_alias_impl_trait)]
26

37
type A = impl Foo;
@@ -10,7 +14,7 @@ fn muh(x: A) -> B {
1014
return Bar; // B's hidden type is Bar
1115
}
1216
x // A's hidden type is `Bar`, because all the hidden types of `B` are compared with each other
13-
//~^ ERROR opaque type's hidden type cannot be another opaque type
17+
//[current]~^ ERROR opaque type's hidden type cannot be another opaque type
1418
}
1519

1620
struct Bar;

‎tests/ui/impl-trait/two_tait_defining_each_other2.stderr ‎tests/ui/impl-trait/two_tait_defining_each_other2.current.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
error: unconstrained opaque type
2-
--> $DIR/two_tait_defining_each_other2.rs:3:10
2+
--> $DIR/two_tait_defining_each_other2.rs:5:10
33
|
44
LL | type A = impl Foo;
55
| ^^^^^^^^
66
|
77
= note: `A` must be used in combination with a concrete type within the same module
88

99
error: opaque type's hidden type cannot be another opaque type from the same scope
10-
--> $DIR/two_tait_defining_each_other2.rs:9:5
10+
--> $DIR/two_tait_defining_each_other2.rs:11:5
1111
|
1212
LL | x // B's hidden type is A (opaquely)
1313
| ^ one of the two opaque types used here has to be outside its defining scope
1414
|
1515
note: opaque type whose hidden type is being assigned
16-
--> $DIR/two_tait_defining_each_other2.rs:4:10
16+
--> $DIR/two_tait_defining_each_other2.rs:6:10
1717
|
1818
LL | type B = impl Foo;
1919
| ^^^^^^^^
2020
note: opaque type being used as hidden type
21-
--> $DIR/two_tait_defining_each_other2.rs:3:10
21+
--> $DIR/two_tait_defining_each_other2.rs:5:10
2222
|
2323
LL | type A = impl Foo;
2424
| ^^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0284]: type annotations needed: cannot satisfy `A <: B`
2+
--> $DIR/two_tait_defining_each_other2.rs:11:5
3+
|
4+
LL | x // B's hidden type is A (opaquely)
5+
| ^ cannot satisfy `A <: B`
6+
7+
error: aborting due to previous error
8+
9+
For more information about this error, try `rustc --explain E0284`.

‎tests/ui/impl-trait/two_tait_defining_each_other2.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
// revisions: current next
2+
//[next] compile-flags: -Ztrait-solver=next
13
#![feature(type_alias_impl_trait)]
24

3-
type A = impl Foo; //~ ERROR unconstrained opaque type
5+
type A = impl Foo; //[current]~ ERROR unconstrained opaque type
46
type B = impl Foo;
57

68
trait Foo {}
79

810
fn muh(x: A) -> B {
911
x // B's hidden type is A (opaquely)
10-
//~^ ERROR opaque type's hidden type cannot be another opaque type
12+
//[current]~^ ERROR opaque type's hidden type cannot be another opaque type
13+
//[next]~^^ ERROR type annotations needed: cannot satisfy `A <: B`
1114
}
1215

1316
struct Bar;

‎tests/ui/impl-trait/two_tait_defining_each_other3.stderr ‎tests/ui/impl-trait/two_tait_defining_each_other3.current.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
error: opaque type's hidden type cannot be another opaque type from the same scope
2-
--> $DIR/two_tait_defining_each_other3.rs:10:16
2+
--> $DIR/two_tait_defining_each_other3.rs:13:16
33
|
44
LL | return x; // B's hidden type is A (opaquely)
55
| ^ one of the two opaque types used here has to be outside its defining scope
66
|
77
note: opaque type whose hidden type is being assigned
8-
--> $DIR/two_tait_defining_each_other3.rs:4:10
8+
--> $DIR/two_tait_defining_each_other3.rs:7:10
99
|
1010
LL | type B = impl Foo;
1111
| ^^^^^^^^
1212
note: opaque type being used as hidden type
13-
--> $DIR/two_tait_defining_each_other3.rs:3:10
13+
--> $DIR/two_tait_defining_each_other3.rs:6:10
1414
|
1515
LL | type A = impl Foo;
1616
| ^^^^^^^^

‎tests/ui/impl-trait/two_tait_defining_each_other3.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// revisions: current next
2+
//[next] compile-flags: -Ztrait-solver=next
3+
//[next] check-pass
14
#![feature(type_alias_impl_trait)]
25

36
type A = impl Foo;
@@ -8,7 +11,7 @@ trait Foo {}
811
fn muh(x: A) -> B {
912
if false {
1013
return x; // B's hidden type is A (opaquely)
11-
//~^ ERROR opaque type's hidden type cannot be another opaque type
14+
//[current]~^ ERROR opaque type's hidden type cannot be another opaque type
1215
}
1316
Bar // A's hidden type is `Bar`, because all the return types are compared with each other
1417
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// check-pass
2+
// compile-flags: -Ztrait-solver=next
3+
4+
fn test<T: Iterator>(x: T::Item) -> impl Sized {
5+
x
6+
}
7+
8+
fn main() {}

‎tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
2-
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
3-
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
4-
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
51
error[E0119]: conflicting implementations of trait `Trait` for type `<LocalTy as Overflow>::Assoc`
62
--> $DIR/trait_ref_is_knowable-norm-overflow.rs:17:1
73
|
@@ -11,7 +7,8 @@ LL | struct LocalTy;
117
LL | impl Trait for <LocalTy as Overflow>::Assoc {}
128
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `<LocalTy as Overflow>::Assoc`
139
|
14-
= note: upstream crates may add a new impl of trait `std::marker::Copy` for type `<LocalTy as Overflow>::Assoc` in future versions
10+
= note: downstream crates may implement trait `std::marker::Sized` for type `<LocalTy as Overflow>::Assoc`
11+
= note: downstream crates may implement trait `std::marker::Copy` for type `<LocalTy as Overflow>::Assoc`
1512

1613
error: aborting due to previous error
1714

‎tests/ui/type-alias-impl-trait/assoc-type-const.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Tests that we properly detect defining usages when using
22
// const generics in an associated opaque type
3-
// check-pass
43

4+
// check-pass
5+
// revisions: current next
6+
//[next] compile-flags: -Ztrait-solver=next
57
#![feature(impl_trait_in_assoc_type)]
68

79
trait UnwrapItemsExt<'a, const C: usize> {

‎tests/ui/type-alias-impl-trait/issue-78450.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// check-pass
2+
// revisions: current next
3+
//[next] compile-flags: -Ztrait-solver=next
24

35
#![feature(impl_trait_in_assoc_type)]
46

‎tests/ui/type-alias-impl-trait/wf-in-associated-type.fail.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0309]: the parameter type `T` may not live long enough
2-
--> $DIR/wf-in-associated-type.rs:36:23
2+
--> $DIR/wf-in-associated-type.rs:38:23
33
|
44
LL | impl<'a, T> Trait<'a, T> for () {
55
| -- the parameter type `T` must be valid for the lifetime `'a` as defined here...
@@ -12,7 +12,7 @@ LL | impl<'a, T: 'a> Trait<'a, T> for () {
1212
| ++++
1313

1414
error[E0309]: the parameter type `T` may not live long enough
15-
--> $DIR/wf-in-associated-type.rs:36:23
15+
--> $DIR/wf-in-associated-type.rs:38:23
1616
|
1717
LL | impl<'a, T> Trait<'a, T> for () {
1818
| -- the parameter type `T` must be valid for the lifetime `'a` as defined here...

‎tests/ui/type-alias-impl-trait/wf-in-associated-type.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// WF check for impl Trait in associated type position.
22
//
3-
// revisions: pass fail
3+
// revisions: pass pass_next fail
44
// [pass] check-pass
5+
// [pass_next] compile-flags: -Ztrait-solver=next
6+
// [pass_next] check-pass
57
// [fail] check-fail
68

79
#![feature(impl_trait_in_assoc_type)]
810

911
// The hidden type here (`&'a T`) requires proving `T: 'a`.
1012
// We know it holds because of implied bounds from the impl header.
11-
#[cfg(pass)]
13+
#[cfg(any(pass, pass_next))]
1214
mod pass {
1315
trait Trait<Req> {
1416
type Opaque1;

0 commit comments

Comments
 (0)
Please sign in to comment.