Skip to content

Commit eb5f6be

Browse files
committed
Support trait upcasting
1 parent 1635ed5 commit eb5f6be

File tree

3 files changed

+253
-51
lines changed

3 files changed

+253
-51
lines changed

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

+137-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,109 @@ 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+
.map(|(super_trait_refs, _)| super_trait_refs)
297+
.into_iter()
298+
.filter(|trait_ref| {
299+
trait_ref.skip_binders().skip_binders().trait_id == principal_b
300+
});
301+
302+
for super_trait_ref in applicable_super_traits {
303+
// `super_trait_ref` is, at this point, quantified over generic params of
304+
// `principal_a` and relevant higher-ranked lifetimes that come from super
305+
// trait elaboration (see comments on `super_traits()`).
306+
//
307+
// So if we have `trait Trait<'a, T>: for<'b> Super<'a, 'b, T> {}`,
308+
// `super_trait_ref` can be something like
309+
// `for<Self, 'a, T> for<'b> Self: Super<'a, 'b, T>`.
310+
//
311+
// We need to convert it into a bound for `DynTy`. We do this by substituting
312+
// bound vars of `principal_trait_ref_a` and then fusing inner binders for
313+
// higher-ranked lifetimes.
314+
let rebound_super_trait_ref = principal_trait_ref_a.map_ref(|q_trait_ref_a| {
315+
q_trait_ref_a
316+
.map_ref(|trait_ref_a| {
317+
super_trait_ref.substitute(interner, &trait_ref_a.substitution)
318+
})
319+
.fuse_binders(interner)
320+
});
321+
322+
// Skip `for<Self>` binder. We'll rebind it immediately below.
323+
let new_principal_trait_ref = rebound_super_trait_ref
324+
.into_value_and_skipped_binders()
325+
.0
326+
.map(|it| it.cast(interner));
327+
328+
// Swap trait ref for `principal_a` with the new trait ref, drop the auto
329+
// traits not included in the upcast target.
330+
let new_source_ty = TyKind::Dyn(DynTy {
331+
bounds: bounds_a.map_ref(|bounds| {
332+
QuantifiedWhereClauses::from_iter(
333+
interner,
334+
bounds.iter(interner).cloned().filter_map(|bound| {
335+
let trait_id = match bound.trait_id() {
336+
Some(id) => id,
337+
None => return Some(bound),
338+
};
339+
340+
if principal_a == trait_id {
341+
Some(new_principal_trait_ref.clone())
342+
} else {
343+
auto_trait_ids_b.contains(&trait_id).then_some(bound)
344+
}
345+
}),
346+
)
256347
}),
257-
)
258-
}),
259-
lifetime: lifetime_b.clone(),
260-
})
261-
.intern(interner);
348+
lifetime: lifetime_b.clone(),
349+
})
350+
.intern(interner);
351+
352+
// Check that new source is equal to target
353+
let eq_goal = EqGoal {
354+
a: new_source_ty.cast(interner),
355+
b: target_ty.clone().cast(interner),
356+
}
357+
.cast(interner);
262358

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),
359+
// We don't push goal for `principal_b`'s object safety because it's implied by
360+
// `principal_a`'s object safety.
361+
builder
362+
.push_clause(trait_ref.clone(), [eq_goal, lifetime_outlives_goal.clone()]);
363+
}
267364
}
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());
278365
}
279366

280367
// T -> dyn Trait + 'a

chalk-solve/src/clauses/super_traits.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,28 @@ pub(super) fn push_trait_super_clauses<I: Interner>(
7373
}
7474
}
7575

76-
fn super_traits<I: Interner>(
76+
/// Returns super-`TraitRef`s and super-`Projection`s that are quantified over the parameters of
77+
/// `trait_id` and relevant higher-ranked lifetimes. The outer `Binders` is for the former and the
78+
/// inner `Binders` is for the latter.
79+
///
80+
/// For example, given the following trait definitions and `C` as `trait_id`,
81+
///
82+
/// ```
83+
/// trait A<'a, T> {}
84+
/// trait B<'b, U> where Self: for<'x> A<'x, U> {}
85+
/// trait C<'c, V> where Self: B<'c, V> {}
86+
/// ```
87+
///
88+
/// returns the following quantified `TraitRef`s.
89+
///
90+
/// ```notrust
91+
/// for<Self, 'c, V> {
92+
/// for<'x> { Self: A<'x, V> }
93+
/// for<> { Self: B<'c, V> }
94+
/// for<> { Self: C<'c, V> }
95+
/// }
96+
/// ```
97+
pub(crate) fn super_traits<I: Interner>(
7798
db: &dyn RustIrDatabase<I>,
7899
trait_id: TraitId<I>,
79100
) -> Binders<(

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)