revives #91068 which has been fixed by only considering implied bounds from projections if they don't normalize while typechecking the function itself
|
// We only add implied bounds for the normalized type as the unnormalized |
|
// type may not actually get checked by the caller. |
|
// |
|
// Can otherwise be unsound, see #91068. |
|
let TypeOpOutput { output: norm_ty, constraints: constraints1, .. } = self |
|
.param_env |
|
.and(type_op::normalize::Normalize::new(ty)) |
|
.fully_perform(self.infcx) |
|
.unwrap_or_else(|_| { |
|
self.infcx |
|
.tcx |
|
.sess |
|
.delay_span_bug(DUMMY_SP, &format!("failed to normalize {:?}", ty)); |
|
TypeOpOutput { |
|
output: self.infcx.tcx.ty_error(), |
|
constraints: None, |
|
error_info: None, |
|
} |
|
}); |
|
// Note: we need this in examples like |
|
// ``` |
|
// trait Foo { |
|
// type Bar; |
|
// fn foo(&self) -> &Self::Bar; |
|
// } |
|
// impl Foo for () { |
|
// type Bar = (); |
|
// fn foo(&self) ->&() {} |
|
// } |
|
// ``` |
|
// Both &Self::Bar and &() are WF |
|
let constraints_implied = self.add_implied_bounds(norm_ty); |
|
normalized_inputs_and_output.push(norm_ty); |
|
constraints1.into_iter().chain(constraints_implied) |
This does not mean that the projection won't normalize when getting called:
trait Trait {
type Type;
}
impl<T> Trait for T {
type Type = ();
}
fn f<'a, 'b>(s: &'b str, _: <&'a &'b () as Trait>::Type) -> &'a str
where
&'a &'b (): Trait, // <- adding this bound is the change from #91068
{
s
}
fn main() {
let x = String::from("Hello World!");
let y = f(&x, ());
drop(x);
println!("{}", y);
}
The added bound prevents <&'a &'b () as Trait>::Type from getting normalized while borrowchecking f, as we prefer param candidates over impl candidates. When calling f, we don't have the &'a &'b (): Trait in our param_env, so we can now freely use the impl candidate to normalize the projection. The caller therefore doesn't have to prove that &'a &'b () is well formed, causing unsoundness.
I am a bit surprised that the caller doesn't have to prove that the &'a &'b (): Trait obligation is well formed, which would cause this example to not be unsound. It doesn't remove the general unsoundness here though. Here's an alternative test where that isn't enough:
trait Trait {
type Type;
}
impl<T> Trait for T {
type Type = ();
}
fn f<'a, 'b>(s: &'b str, _: <&'a &'b () as Trait>::Type) -> &'a str
where
for<'what, 'ever> &'what &'ever (): Trait,
{
s
}
fn main() {
let x = String::from("Hello World!");
let y = f(&x, ());
drop(x);
println!("{}", y);
}
revives #91068 which has been fixed by only considering implied bounds from projections if they don't normalize while typechecking the function itself
rust/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
Lines 256 to 289 in 7125846
This does not mean that the projection won't normalize when getting called:
The added bound prevents
<&'a &'b () as Trait>::Typefrom getting normalized while borrowcheckingf, as we prefer param candidates over impl candidates. When callingf, we don't have the&'a &'b (): Traitin ourparam_env, so we can now freely use the impl candidate to normalize the projection. The caller therefore doesn't have to prove that&'a &'b ()is well formed, causing unsoundness.I am a bit surprised that the caller doesn't have to prove that the
&'a &'b (): Traitobligation is well formed, which would cause this example to not be unsound. It doesn't remove the general unsoundness here though. Here's an alternative test where that isn't enough: