Skip to content

Commit 68e7b98

Browse files
WaffleLapkinlowr
andcommitted
Implement trait upcasting
Co-authored-by: Ryo Yoshida <[email protected]>
1 parent a56820b commit 68e7b98

File tree

2 files changed

+254
-68
lines changed

2 files changed

+254
-68
lines changed

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

Lines changed: 160 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,27 @@ fn uses_outer_binder_params<I: Interner>(
137137
matches!(flow, ControlFlow::Break(_))
138138
}
139139

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

153163
fn auto_trait_ids<'a, I: Interner>(
@@ -202,8 +212,12 @@ pub fn add_unsize_program_clauses<I: Interner>(
202212
lifetime: lifetime_b,
203213
}),
204214
) => {
205-
let principal_a = principal_id(db, bounds_a);
206-
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);
207221

208222
// Include super traits in a list of auto traits for A,
209223
// to allow `dyn Trait -> dyn Trait + X` if `Trait: X`.
@@ -233,6 +247,13 @@ pub fn add_unsize_program_clauses<I: Interner>(
233247
return;
234248
}
235249

250+
// Check that source lifetime outlives target lifetime
251+
let lifetime_outlives_goal: Goal<I> = WhereClause::LifetimeOutlives(LifetimeOutlives {
252+
a: lifetime_a.clone(),
253+
b: lifetime_b.clone(),
254+
})
255+
.cast(interner);
256+
236257
// COMMENT FROM RUSTC:
237258
// ------------------
238259
// Require that the traits involved in this upcast are **equal**;
@@ -250,67 +271,138 @@ pub fn add_unsize_program_clauses<I: Interner>(
250271
// with what our behavior should be there. -nikomatsakis
251272
// ------------------
252273

253-
// Construct a new trait object type by taking the source ty,
254-
// replacing auto traits of source with those of target,
255-
// and changing source lifetime to target lifetime.
256-
//
257-
// In order for the coercion to be valid, this new type
258-
// should be equal to target type.
259-
let new_source_ty = TyKind::Dyn(DynTy {
260-
bounds: bounds_a.map_ref(|bounds| {
261-
QuantifiedWhereClauses::from_iter(
262-
interner,
263-
bounds
264-
.iter(interner)
265-
.cloned()
266-
.filter_map(|bound| {
267-
let Some(trait_id) = bound.trait_id() else {
268-
// Keep non-"implements" bounds as-is
269-
return Some(bound);
270-
};
271-
272-
// Auto traits are already checked above, ignore them
273-
// (we'll use the ones from B below)
274-
if db.trait_datum(trait_id).is_auto_trait() {
275-
return None;
276-
}
277-
278-
// The only "implements" bound that is not an auto trait, is the principal
279-
assert_eq!(Some(trait_id), principal_a);
280-
281-
// Only include principal_a if the principal_b is also present
282-
// (this allows dropping principal, `dyn Tr+A -> dyn A`)
283-
principal_b.is_some().then(|| bound)
274+
if principal_a == principal_b || principal_b.is_none() {
275+
// Construct a new trait object type by taking the source ty,
276+
// replacing auto traits of source with those of target,
277+
// and changing source lifetime to target lifetime.
278+
//
279+
// In order for the coercion to be valid, this new type
280+
// should be equal to target type.
281+
let new_source_ty = TyKind::Dyn(DynTy {
282+
bounds: bounds_a.map_ref(|bounds| {
283+
QuantifiedWhereClauses::from_iter(
284+
interner,
285+
bounds
286+
.iter(interner)
287+
.cloned()
288+
.filter_map(|bound| {
289+
let Some(trait_id) = bound.trait_id() else {
290+
// Keep non-"implements" bounds as-is
291+
return Some(bound);
292+
};
293+
294+
// Auto traits are already checked above, ignore them
295+
// (we'll use the ones from B below)
296+
if db.trait_datum(trait_id).is_auto_trait() {
297+
return None;
298+
}
299+
300+
// The only "implements" bound that is not an auto trait, is the principal
301+
assert_eq!(Some(trait_id), principal_a);
302+
303+
// Only include principal_a if the principal_b is also present
304+
// (this allows dropping principal, `dyn Tr+A -> dyn A`)
305+
principal_b.is_some().then(|| bound)
306+
})
307+
// Add auto traits from B (again, they are already checked above).
308+
.chain(bounds_b.skip_binders().iter(interner).cloned().filter(
309+
|bound| {
310+
bound.trait_id().is_some_and(|trait_id| {
311+
db.trait_datum(trait_id).is_auto_trait()
312+
})
313+
},
314+
)),
315+
)
316+
}),
317+
lifetime: lifetime_b.clone(),
318+
})
319+
.intern(interner);
320+
321+
// Check that new source is equal to target
322+
let eq_goal = EqGoal {
323+
a: new_source_ty.cast(interner),
324+
b: target_ty.clone().cast(interner),
325+
}
326+
.cast(interner);
327+
328+
builder.push_clause(trait_ref, [eq_goal, lifetime_outlives_goal].iter());
329+
} else {
330+
// Conditions above imply that both of these are always `Some`
331+
// (b != None, b is Some iff a is Some).
332+
let principal_a = principal_a.unwrap();
333+
let principal_b = principal_b.unwrap();
334+
335+
let principal_trait_ref_a = principal_trait_ref_a.unwrap();
336+
let applicable_super_traits = super_traits(db, principal_a)
337+
.map(|(super_trait_refs, _)| super_trait_refs)
338+
.into_iter()
339+
.filter(|trait_ref| {
340+
trait_ref.skip_binders().skip_binders().trait_id == principal_b
341+
});
342+
343+
for super_trait_ref in applicable_super_traits {
344+
// `super_trait_ref` is, at this point, quantified over generic params of
345+
// `principal_a` and relevant higher-ranked lifetimes that come from super
346+
// trait elaboration (see comments on `super_traits()`).
347+
//
348+
// So if we have `trait Trait<'a, T>: for<'b> Super<'a, 'b, T> {}`,
349+
// `super_trait_ref` can be something like
350+
// `for<Self, 'a, T> for<'b> Self: Super<'a, 'b, T>`.
351+
//
352+
// We need to convert it into a bound for `DynTy`. We do this by substituting
353+
// bound vars of `principal_trait_ref_a` and then fusing inner binders for
354+
// higher-ranked lifetimes.
355+
let rebound_super_trait_ref = principal_trait_ref_a.map_ref(|q_trait_ref_a| {
356+
q_trait_ref_a
357+
.map_ref(|trait_ref_a| {
358+
super_trait_ref.substitute(interner, &trait_ref_a.substitution)
284359
})
285-
// Add auto traits from B (again, they are already checked above).
286-
.chain(bounds_b.skip_binders().iter(interner).cloned().filter(
287-
|bound| {
288-
bound.trait_id().is_some_and(|trait_id| {
289-
db.trait_datum(trait_id).is_auto_trait()
290-
})
291-
},
292-
)),
293-
)
294-
}),
295-
lifetime: lifetime_b.clone(),
296-
})
297-
.intern(interner);
360+
.fuse_binders(interner)
361+
});
298362

299-
// Check that new source is equal to target
300-
let eq_goal = EqGoal {
301-
a: new_source_ty.cast(interner),
302-
b: target_ty.clone().cast(interner),
303-
}
304-
.cast(interner);
305-
306-
// Check that source lifetime outlives target lifetime
307-
let lifetime_outlives_goal: Goal<I> = WhereClause::LifetimeOutlives(LifetimeOutlives {
308-
a: lifetime_a.clone(),
309-
b: lifetime_b.clone(),
310-
})
311-
.cast(interner);
363+
// Skip `for<Self>` binder. We'll rebind it immediately below.
364+
let new_principal_trait_ref = rebound_super_trait_ref
365+
.into_value_and_skipped_binders()
366+
.0
367+
.map(|it| it.cast(interner));
368+
369+
// Swap trait ref for `principal_a` with the new trait ref, drop the auto
370+
// traits not included in the upcast target.
371+
let new_source_ty = TyKind::Dyn(DynTy {
372+
bounds: bounds_a.map_ref(|bounds| {
373+
QuantifiedWhereClauses::from_iter(
374+
interner,
375+
bounds.iter(interner).cloned().filter_map(|bound| {
376+
let trait_id = match bound.trait_id() {
377+
Some(id) => id,
378+
None => return Some(bound),
379+
};
380+
381+
if principal_a == trait_id {
382+
Some(new_principal_trait_ref.clone())
383+
} else {
384+
auto_trait_ids_b.contains(&trait_id).then_some(bound)
385+
}
386+
}),
387+
)
388+
}),
389+
lifetime: lifetime_b.clone(),
390+
})
391+
.intern(interner);
392+
393+
// Check that new source is equal to target
394+
let eq_goal = EqGoal {
395+
a: new_source_ty.cast(interner),
396+
b: target_ty.clone().cast(interner),
397+
}
398+
.cast(interner);
312399

313-
builder.push_clause(trait_ref, [eq_goal, lifetime_outlives_goal].iter());
400+
// We don't push goal for `principal_b`'s object safety because it's implied by
401+
// `principal_a`'s object safety.
402+
builder
403+
.push_clause(trait_ref.clone(), [eq_goal, lifetime_outlives_goal.clone()]);
404+
}
405+
}
314406
}
315407

316408
// T -> dyn Trait + 'a

tests/test/unsize.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,100 @@ fn super_auto_trait() {
179179
}
180180
}
181181

182+
#[test]
183+
fn dyn_upcasting() {
184+
test! {
185+
program {
186+
#[lang(unsize)]
187+
trait Unsize<T> {}
188+
189+
#[object_safe]
190+
trait SuperSuper {}
191+
#[object_safe]
192+
trait GenericSuper<T> {}
193+
#[object_safe]
194+
trait Super
195+
where
196+
Self: SuperSuper,
197+
Self: GenericSuper<i32>,
198+
Self: GenericSuper<i64>,
199+
{}
200+
#[object_safe]
201+
trait Principal where Self: Super {}
202+
203+
#[auto]
204+
#[object_safe]
205+
trait Auto1 {}
206+
207+
#[auto]
208+
#[object_safe]
209+
trait Auto2 {}
210+
}
211+
212+
goal {
213+
forall<'a> {
214+
dyn Principal + 'a: Unsize<dyn Super + 'a>
215+
}
216+
} yields {
217+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
218+
}
219+
220+
goal {
221+
forall<'a> {
222+
dyn Principal + Auto1 + 'a: Unsize<dyn Super + Auto1 + 'a>
223+
}
224+
} yields {
225+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
226+
}
227+
228+
// Different set of auto traits
229+
goal {
230+
forall<'a> {
231+
dyn Principal + Auto1 + 'a: Unsize<dyn Super + Auto2 + 'a>
232+
}
233+
} yields {
234+
expect![[r#"No possible solution"#]]
235+
}
236+
237+
// Dropping auto traits is allowed
238+
goal {
239+
forall<'a> {
240+
dyn Principal + Auto1 + Auto2 + 'a: Unsize<dyn Super + Auto1 + 'a>
241+
}
242+
} yields {
243+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
244+
}
245+
246+
// Upcasting to indirect super traits
247+
goal {
248+
forall<'a> {
249+
dyn Principal + 'a: Unsize<dyn SuperSuper + 'a>
250+
}
251+
} yields {
252+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
253+
}
254+
255+
goal {
256+
forall<'a> {
257+
dyn Principal + 'a: Unsize<dyn GenericSuper<i32> + 'a>
258+
}
259+
} yields {
260+
expect![[r#"Unique; lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]"#]]
261+
}
262+
263+
// Ambiguous if there are multiple super traits applicable
264+
goal {
265+
exists<T> {
266+
forall<'a> {
267+
dyn Principal + 'a: Unsize<dyn GenericSuper<T> + 'a>
268+
}
269+
}
270+
} yields {
271+
expect![[r#"Ambiguous; no inference guidance"#]]
272+
}
273+
}
274+
}
275+
182276
#[test]
183277
fn ty_to_dyn_unsizing() {
184278
test! {

0 commit comments

Comments
 (0)