Skip to content

Commit ac1a0d7

Browse files
committed
Support trait upcasting
1 parent df8aa32 commit ac1a0d7

File tree

3 files changed

+251
-50
lines changed

3 files changed

+251
-50
lines changed

chalk-solve/src/clauses/builtin_traits/unsize.rs

+136-50
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashSet;
22
use std::iter;
33
use std::ops::ControlFlow;
44

5-
use crate::clauses::ClauseBuilder;
5+
use crate::clauses::{super_traits::super_traits, ClauseBuilder};
66
use crate::rust_ir::AdtKind;
77
use crate::{Interner, RustIrDatabase, TraitRef, WellKnownTrait};
88
use chalk_ir::{
@@ -136,17 +136,27 @@ fn uses_outer_binder_params<I: Interner>(
136136
matches!(flow, ControlFlow::Break(_))
137137
}
138138

139-
fn principal_id<I: Interner>(
139+
fn principal_trait_ref<I: Interner>(
140140
db: &dyn RustIrDatabase<I>,
141141
bounds: &Binders<QuantifiedWhereClauses<I>>,
142-
) -> Option<TraitId<I>> {
143-
let interner = db.interner();
144-
142+
) -> Option<Binders<Binders<TraitRef<I>>>> {
145143
bounds
146-
.skip_binders()
147-
.iter(interner)
148-
.filter_map(|b| b.trait_id())
149-
.find(|&id| !db.trait_datum(id).is_auto_trait())
144+
.map_ref(|b| b.iter(db.interner()))
145+
.into_iter()
146+
.find_map(|b| {
147+
b.filter_map(|qwc| {
148+
qwc.as_ref().filter_map(|wc| match wc {
149+
WhereClause::Implemented(trait_ref) => {
150+
if db.trait_datum(trait_ref.trait_id).is_auto_trait() {
151+
None
152+
} else {
153+
Some(trait_ref.clone())
154+
}
155+
}
156+
_ => None,
157+
})
158+
})
159+
})
150160
}
151161

152162
fn auto_trait_ids<'a, I: Interner>(
@@ -191,6 +201,7 @@ pub fn add_unsize_program_clauses<I: Interner>(
191201

192202
match (source_ty.kind(interner), target_ty.kind(interner)) {
193203
// dyn Trait + AutoX + 'a -> dyn Trait + AutoY + 'b
204+
// dyn TraitA + AutoX + 'a -> dyn TraitB + AutoY + 'b (upcasting)
194205
(
195206
TyKind::Dyn(DynTy {
196207
bounds: bounds_a,
@@ -201,21 +212,30 @@ pub fn add_unsize_program_clauses<I: Interner>(
201212
lifetime: lifetime_b,
202213
}),
203214
) => {
204-
let principal_a = principal_id(db, bounds_a);
205-
let principal_b = principal_id(db, bounds_b);
215+
let principal_trait_ref_a = principal_trait_ref(db, bounds_a);
216+
let principal_a = principal_trait_ref_a
217+
.as_ref()
218+
.map(|trait_ref| trait_ref.skip_binders().skip_binders().trait_id);
219+
let principal_b = principal_trait_ref(db, bounds_b)
220+
.map(|trait_ref| trait_ref.skip_binders().skip_binders().trait_id);
206221

207222
let auto_trait_ids_a: Vec<_> = auto_trait_ids(db, bounds_a).collect();
208223
let auto_trait_ids_b: Vec<_> = auto_trait_ids(db, bounds_b).collect();
209224

210-
let may_apply = principal_a == principal_b
211-
&& auto_trait_ids_b
212-
.iter()
213-
.all(|id_b| auto_trait_ids_a.iter().any(|id_a| id_a == id_b));
214-
215-
if !may_apply {
225+
let auto_traits_compatible = auto_trait_ids_a
226+
.iter()
227+
.all(|id_b| auto_trait_ids_a.contains(&id_b));
228+
if !auto_traits_compatible {
216229
return;
217230
}
218231

232+
// Check that source lifetime outlives target lifetime
233+
let lifetime_outlives_goal: Goal<I> = WhereClause::LifetimeOutlives(LifetimeOutlives {
234+
a: lifetime_a.clone(),
235+
b: lifetime_b.clone(),
236+
})
237+
.cast(interner);
238+
219239
// COMMENT FROM RUSTC:
220240
// ------------------
221241
// Require that the traits involved in this upcast are **equal**;
@@ -239,42 +259,108 @@ pub fn add_unsize_program_clauses<I: Interner>(
239259
//
240260
// In order for the coercion to be valid, this new type
241261
// should be equal to target type.
242-
let new_source_ty = TyKind::Dyn(DynTy {
243-
bounds: bounds_a.map_ref(|bounds| {
244-
QuantifiedWhereClauses::from_iter(
245-
interner,
246-
bounds.iter(interner).filter(|bound| {
247-
let trait_id = match bound.trait_id() {
248-
Some(id) => id,
249-
None => return true,
250-
};
251-
252-
if auto_trait_ids_a.iter().all(|&id_a| id_a != trait_id) {
253-
return true;
254-
}
255-
auto_trait_ids_b.iter().any(|&id_b| id_b == trait_id)
262+
if principal_a == principal_b {
263+
let new_source_ty = TyKind::Dyn(DynTy {
264+
bounds: bounds_a.map_ref(|bounds| {
265+
QuantifiedWhereClauses::from_iter(
266+
interner,
267+
bounds.iter(interner).filter(|bound| {
268+
let trait_id = match bound.trait_id() {
269+
Some(id) => id,
270+
None => return true,
271+
};
272+
273+
if !auto_trait_ids_a.contains(&trait_id) {
274+
return true;
275+
}
276+
auto_trait_ids_b.contains(&trait_id)
277+
}),
278+
)
279+
}),
280+
lifetime: lifetime_b.clone(),
281+
})
282+
.intern(interner);
283+
284+
// Check that new source is equal to target
285+
let eq_goal = EqGoal {
286+
a: new_source_ty.cast(interner),
287+
b: target_ty.clone().cast(interner),
288+
}
289+
.cast(interner);
290+
291+
builder.push_clause(trait_ref, [eq_goal, lifetime_outlives_goal]);
292+
} else if let (Some(principal_a), Some(principal_b)) = (principal_a, principal_b) {
293+
let principal_trait_ref_a = principal_trait_ref_a.unwrap();
294+
let applicable_super_traits =
295+
super_traits(db, principal_a)
296+
.into_iter()
297+
.filter(|trait_ref| {
298+
trait_ref.skip_binders().skip_binders().trait_id == principal_b
299+
});
300+
301+
for super_trait_ref in applicable_super_traits {
302+
// `super_trait_ref` is, at this point, quantified over generic params of
303+
// `principal_a` and relevant higher-ranked lifetimes that come from super
304+
// trait elaboration (see comments on `super_traits()`).
305+
//
306+
// So if we have `trait Trait<'a, T>: for<'b> Super<'a, 'b, T> {}`,
307+
// `super_trait_ref` can be something like
308+
// `for<Self, 'a, T> for<'b> Self: Super<'a, 'b, T>`.
309+
//
310+
// We need to convert it into a bound for `DynTy`. We do this by substituting
311+
// bound vars of `principal_trait_ref_a` and then fusing inner binders for
312+
// higher-ranked lifetimes.
313+
let rebound_super_trait_ref = principal_trait_ref_a.map_ref(|q_trait_ref_a| {
314+
q_trait_ref_a
315+
.map_ref(|trait_ref_a| {
316+
super_trait_ref.substitute(interner, &trait_ref_a.substitution)
317+
})
318+
.fuse_binders(interner)
319+
});
320+
321+
// Skip `for<Self>` binder. We'll rebind it immediately below.
322+
let new_principal_trait_ref = rebound_super_trait_ref
323+
.into_value_and_skipped_binders()
324+
.0
325+
.map(|it| it.cast(interner));
326+
327+
// Swap trait ref for `principal_a` with the new trait ref, drop the auto
328+
// traits not included in the upcast target.
329+
let new_source_ty = TyKind::Dyn(DynTy {
330+
bounds: bounds_a.map_ref(|bounds| {
331+
QuantifiedWhereClauses::from_iter(
332+
interner,
333+
bounds.iter(interner).cloned().filter_map(|bound| {
334+
let trait_id = match bound.trait_id() {
335+
Some(id) => id,
336+
None => return Some(bound),
337+
};
338+
339+
if principal_a == trait_id {
340+
Some(new_principal_trait_ref.clone())
341+
} else {
342+
auto_trait_ids_b.contains(&trait_id).then_some(bound)
343+
}
344+
}),
345+
)
256346
}),
257-
)
258-
}),
259-
lifetime: lifetime_b.clone(),
260-
})
261-
.intern(interner);
347+
lifetime: lifetime_b.clone(),
348+
})
349+
.intern(interner);
350+
351+
// Check that new source is equal to target
352+
let eq_goal = EqGoal {
353+
a: new_source_ty.cast(interner),
354+
b: target_ty.clone().cast(interner),
355+
}
356+
.cast(interner);
262357

263-
// Check that new source is equal to target
264-
let eq_goal = EqGoal {
265-
a: new_source_ty.cast(interner),
266-
b: target_ty.clone().cast(interner),
358+
// We don't push goal for `principal_b`'s object safety because it's implied by
359+
// `principal_a`'s object safety.
360+
builder
361+
.push_clause(trait_ref.clone(), [eq_goal, lifetime_outlives_goal.clone()]);
362+
}
267363
}
268-
.cast(interner);
269-
270-
// Check that source lifetime outlives target lifetime
271-
let lifetime_outlives_goal: Goal<I> = WhereClause::LifetimeOutlives(LifetimeOutlives {
272-
a: lifetime_a.clone(),
273-
b: lifetime_b.clone(),
274-
})
275-
.cast(interner);
276-
277-
builder.push_clause(trait_ref, [eq_goal, lifetime_outlives_goal].iter());
278364
}
279365

280366
// T -> dyn Trait + 'a

chalk-solve/src/clauses/super_traits.rs

+21
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ pub(super) fn push_trait_super_clauses<I: Interner>(
3939
}
4040
}
4141

42+
/// Returns super-`TraitRef`s that are quantified over the parameters of `trait_id` and relevant
43+
/// higher-ranked lifetimes. The outer `Binders` is for the former and the inner `Binders` is for
44+
/// the latter.
45+
///
46+
/// For example, given the following trait definitions and `C` as `trait_id`,
47+
///
48+
/// ```
49+
/// trait A<'a, T> {}
50+
/// trait B<'b, U> where Self: for<'x> A<'x, U> {}
51+
/// trait C<'c, V> where Self: B<'c, V> {}
52+
/// ```
53+
///
54+
/// returns the following quantified `TraitRef`s.
55+
///
56+
/// ```notrust
57+
/// for<Self, 'c, V> {
58+
/// for<'x> { Self: A<'x, V> }
59+
/// for<> { Self: B<'c, V> }
60+
/// for<> { Self: C<'c, V> }
61+
/// }
62+
/// ```
4263
pub fn super_traits<I: Interner>(
4364
db: &dyn RustIrDatabase<I>,
4465
trait_id: TraitId<I>,

tests/test/unsize.rs

+94
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,100 @@ fn dyn_to_dyn_unsizing() {
142142
}
143143
}
144144

145+
#[test]
146+
fn dyn_upcasting() {
147+
test! {
148+
program {
149+
#[lang(unsize)]
150+
trait Unsize<T> {}
151+
152+
#[object_safe]
153+
trait Super {}
154+
#[object_safe]
155+
trait GenericSuper<T> {}
156+
#[object_safe]
157+
trait Sub
158+
where
159+
Self: Super,
160+
Self: GenericSuper<i32>,
161+
Self: GenericSuper<i64>,
162+
{}
163+
#[object_safe]
164+
trait Principal where Self: Sub {}
165+
166+
#[auto]
167+
#[object_safe]
168+
trait Auto1 {}
169+
170+
#[auto]
171+
#[object_safe]
172+
trait Auto2 {}
173+
}
174+
175+
goal {
176+
forall<'a> {
177+
dyn Principal + 'a: Unsize<dyn Sub + 'a>
178+
}
179+
} yields {
180+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
181+
}
182+
183+
goal {
184+
forall<'a> {
185+
dyn Principal + Auto1 + 'a: Unsize<dyn Sub + Auto1 + 'a>
186+
}
187+
} yields {
188+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
189+
}
190+
191+
// Different set of auto traits
192+
goal {
193+
forall<'a> {
194+
dyn Principal + Auto1 + 'a: Unsize<dyn Sub + Auto2 + 'a>
195+
}
196+
} yields {
197+
expect![[r#"No possible solution"#]]
198+
}
199+
200+
// Dropping auto traits is allowed
201+
goal {
202+
forall<'a> {
203+
dyn Principal + Auto1 + Auto2 + 'a: Unsize<dyn Sub + Auto1 + 'a>
204+
}
205+
} yields {
206+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
207+
}
208+
209+
// Upcasting to indirect super traits
210+
goal {
211+
forall<'a> {
212+
dyn Principal + 'a: Unsize<dyn Super + 'a>
213+
}
214+
} yields {
215+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
216+
}
217+
218+
goal {
219+
forall<'a> {
220+
dyn Principal + 'a: Unsize<dyn GenericSuper<i32> + 'a>
221+
}
222+
} yields {
223+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
224+
}
225+
226+
// Ambiguous if there are multiple super traits applicable
227+
goal {
228+
exists<T> {
229+
forall<'a> {
230+
dyn Principal + 'a: Unsize<dyn GenericSuper<T> + 'a>
231+
}
232+
}
233+
} yields {
234+
expect![[r#"Ambiguous; no inference guidance"#]]
235+
}
236+
}
237+
}
238+
145239
#[test]
146240
fn ty_to_dyn_unsizing() {
147241
test! {

0 commit comments

Comments
 (0)