This commit is contained in:
Boxy Uwu 2025-12-03 14:53:03 +00:00
parent 24a3f58ade
commit 2f95ecfdd6
7 changed files with 61 additions and 122 deletions

View file

@ -113,9 +113,15 @@ fn success<'tcx>(
Ok(InferOk { value: (adj, target), obligations })
}
enum LeakCheck {
/// Whether to force a leak check to occur in `Coerce::unify_raw`.
/// Note that leak checks may still occur evn with `ForceLeakCheck::No`.
///
/// FIXME: We may want to change type relations to always leak-check
/// after exiting a binder, at which point we will always do so and
/// no longer need to handle this explicitly
enum ForceLeakCheck {
Yes,
Default,
No,
}
impl<'f, 'tcx> Coerce<'f, 'tcx> {
@ -132,7 +138,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
&self,
a: Ty<'tcx>,
b: Ty<'tcx>,
leak_check: LeakCheck,
leak_check: ForceLeakCheck,
) -> InferResult<'tcx, Ty<'tcx>> {
debug!("unify(a: {:?}, b: {:?}, use_lub: {})", a, b, self.use_lub);
self.commit_if_ok(|snapshot| {
@ -176,10 +182,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// In order to actually ensure that equating the binders *does*
// result in equal binders, and that the lhs is actually a supertype
// of the rhs, we must perform a leak check here.
//
// FIXME: Type relations should handle leak checks
// themselves whenever a binder is entered.
if matches!(leak_check, LeakCheck::Yes) {
if matches!(leak_check, ForceLeakCheck::Yes) {
self.leak_check(outer_universe, Some(snapshot))?;
}
@ -188,7 +191,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
}
/// Unify two types (using sub or lub).
fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>, leak_check: LeakCheck) -> CoerceResult<'tcx> {
fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>, leak_check: ForceLeakCheck) -> CoerceResult<'tcx> {
self.unify_raw(a, b, leak_check)
.and_then(|InferOk { value: ty, obligations }| success(vec![], ty, obligations))
}
@ -200,7 +203,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
b: Ty<'tcx>,
adjustments: impl IntoIterator<Item = Adjustment<'tcx>>,
final_adjustment: Adjust,
leak_check: LeakCheck,
leak_check: ForceLeakCheck,
) -> CoerceResult<'tcx> {
self.unify_raw(a, b, leak_check).and_then(|InferOk { value: ty, obligations }| {
success(
@ -231,7 +234,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
);
} else {
// Otherwise the only coercion we can do is unification.
return self.unify(a, b, LeakCheck::Default);
return self.unify(a, b, ForceLeakCheck::No);
}
}
@ -265,7 +268,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
return self.coerce_to_raw_ptr(a, b, b_mutbl);
}
ty::Ref(r_b, _, mutbl_b) => {
return self.coerce_to_ref(a, b, mutbl_b, r_b);
return self.coerce_to_ref(a, b, r_b, mutbl_b);
}
ty::Adt(pin, _)
if self.tcx.features().pin_ergonomics()
@ -301,7 +304,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
}
_ => {
// Otherwise, just use unification rules.
self.unify(a, b, LeakCheck::Default)
self.unify(a, b, ForceLeakCheck::No)
}
}
}
@ -325,12 +328,19 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
));
};
let target_ty = self.use_lub.then(|| self.next_ty_var(self.cause.span)).unwrap_or(b);
push_coerce_obligation(a, target_ty);
if self.use_lub {
let target_ty = if self.use_lub {
// When computing the lub, we create a new target
// and coerce both `a` and `b` to it.
let target_ty = self.next_ty_var(self.cause.span);
push_coerce_obligation(a, target_ty);
push_coerce_obligation(b, target_ty);
}
target_ty
} else {
// When subtyping, we don't need to create a new target
// as we only coerce `a` to `b`.
push_coerce_obligation(a, b);
b
};
debug!(
"coerce_from_inference_variable: two inference variables, target_ty={:?}, obligations={:?}",
@ -340,7 +350,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
} else {
// One unresolved type variable: just apply subtyping, we may be able
// to do something useful.
self.unify(a, b, LeakCheck::Default)
self.unify(a, b, ForceLeakCheck::No)
}
}
@ -355,8 +365,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
&self,
a: Ty<'tcx>,
b: Ty<'tcx>,
mutbl_b: hir::Mutability,
r_b: ty::Region<'tcx>,
mutbl_b: hir::Mutability,
) -> CoerceResult<'tcx> {
debug!("coerce_to_ref(a={:?}, b={:?})", a, b);
debug_assert!(self.shallow_resolve(a) == a);
@ -367,7 +377,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
coerce_mutbls(mutbl, mutbl_b)?;
(r_a, ty::TypeAndMut { ty, mutbl })
}
_ => return self.unify(a, b, LeakCheck::Default),
_ => return self.unify(a, b, ForceLeakCheck::No),
};
// Look at each step in the `Deref` chain and check if
@ -418,7 +428,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// the `Target` type with the pointee of `b`. This is necessary
// to properly account for the differing variances of the pointees
// of `&` vs `&mut` references.
match self.unify_raw(autorefd_deref_ty, b, LeakCheck::Default) {
match self.unify_raw(autorefd_deref_ty, b, ForceLeakCheck::No) {
Ok(ok) => Some(ok),
Err(err) => {
if first_error.is_none() {
@ -473,10 +483,13 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
obligations.extend(o);
obligations.extend(autoderef.into_obligations());
assert!(
matches!(coerced_a.kind(), ty::Ref(..)),
"expected a ref type, got {:?}",
coerced_a
);
// Now apply the autoref
let ty::Ref(..) = coerced_a.kind() else {
span_bug!(self.cause.span, "expected a ref type, got {:?}", coerced_a);
};
let mutbl = AutoBorrowMutability::new(mutbl_b, self.allow_two_phase);
adjustments
.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), target: coerced_a });
@ -615,7 +628,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
target,
reborrow.into_iter().flat_map(|(deref, autoref)| [deref, autoref]),
Adjust::Pointer(PointerCoercion::Unsize),
LeakCheck::Default,
ForceLeakCheck::No,
)?;
// Create an obligation for `Source: CoerceUnsized<Target>`.
@ -830,7 +843,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// To complete the reborrow, we need to make sure we can unify the inner types, and if so we
// add the adjustments.
self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), LeakCheck::Default)
self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), ForceLeakCheck::No)
}
fn coerce_from_fn_pointer(
@ -846,9 +859,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
ty::FnPtr(_, b_hdr) if a_sig.safety().is_safe() && b_hdr.safety.is_unsafe() => {
let a = self.tcx.safe_to_unsafe_fn_ty(a_sig);
let adjust = Adjust::Pointer(PointerCoercion::UnsafeFnPointer);
self.unify_and(a, b, [], adjust, LeakCheck::Yes)
self.unify_and(a, b, [], adjust, ForceLeakCheck::Yes)
}
_ => self.unify(a, b, LeakCheck::Yes),
_ => self.unify(a, b, ForceLeakCheck::Yes),
}
}
@ -861,20 +874,18 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
ty::FnPtr(_, b_hdr) => {
let a_sig = self.sig_for_fn_def_coercion(a, Some(b_hdr.safety))?;
// FIXME: we shouldn't be normalizing here as coercion is inside of
// a probe. This can probably cause ICEs.
let InferOk { value: a_sig, mut obligations } =
self.at(&self.cause, self.param_env).normalize(a_sig);
let a = Ty::new_fn_ptr(self.tcx, a_sig);
let adjust = Adjust::Pointer(PointerCoercion::ReifyFnPointer(b_hdr.safety));
let InferOk { value, obligations: o2 } =
self.unify_and(a, b, [], adjust, LeakCheck::Yes)?;
self.unify_and(a, b, [], adjust, ForceLeakCheck::Yes)?;
obligations.extend(o2);
Ok(InferOk { value, obligations })
}
_ => self.unify(a, b, LeakCheck::Default),
_ => self.unify(a, b, ForceLeakCheck::No),
}
}
@ -893,9 +904,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
debug!("coerce_closure_to_fn(a={:?}, b={:?}, pty={:?})", a, b, pointer_ty);
let adjust = Adjust::Pointer(PointerCoercion::ClosureFnPointer(safety));
self.unify_and(pointer_ty, b, [], adjust, LeakCheck::Default)
self.unify_and(pointer_ty, b, [], adjust, ForceLeakCheck::No)
}
_ => self.unify(a, b, LeakCheck::Default),
_ => self.unify(a, b, ForceLeakCheck::No),
}
}
@ -912,7 +923,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
let (is_ref, mt_a) = match *a.kind() {
ty::Ref(_, ty, mutbl) => (true, ty::TypeAndMut { ty, mutbl }),
ty::RawPtr(ty, mutbl) => (false, ty::TypeAndMut { ty, mutbl }),
_ => return self.unify(a, b, LeakCheck::Default),
_ => return self.unify(a, b, ForceLeakCheck::No),
};
coerce_mutbls(mt_a.mutbl, mutbl_b)?;
@ -927,7 +938,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
b,
[Adjustment { kind: Adjust::Deref(None), target: mt_a.ty }],
Adjust::Borrow(AutoBorrow::RawPtr(mutbl_b)),
LeakCheck::Default,
ForceLeakCheck::No,
)
} else if mt_a.mutbl != mutbl_b {
self.unify_and(
@ -935,10 +946,10 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
b,
[],
Adjust::Pointer(PointerCoercion::MutToConstPointer),
LeakCheck::Default,
ForceLeakCheck::No,
)
} else {
self.unify(a_raw, b, LeakCheck::Default)
self.unify(a_raw, b, ForceLeakCheck::No)
}
}
}
@ -1037,7 +1048,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// We don't ever need two-phase here since we throw out the result of the coercion.
let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true);
coerce.autoderef(DUMMY_SP, expr_ty).find_map(|(ty, steps)| {
self.probe(|_| coerce.unify_raw(ty, target, LeakCheck::Default)).ok().map(|_| steps)
self.probe(|_| coerce.unify_raw(ty, target, ForceLeakCheck::No)).ok().map(|_| steps)
})
}

View file

@ -1,3 +1,5 @@
// Regression test for #132765
//
// We have two function parameters with types:
// - `&?0`
// - `Box<for<'a> fn(<?0 as Trait<'a>>::Item)>`

View file

@ -1,5 +1,5 @@
error[E0277]: the trait bound `(): LendingIterator` is not satisfied
--> $DIR/hr_alias_normalization_leaking_vars.rs:32:31
--> $DIR/hr_alias_normalization_leaking_vars.rs:34:31
|
LL | LendingIterator::for_each(&(), f);
| ------------------------- ^^^ the trait `LendingIterator` is not implemented for `()`
@ -7,13 +7,13 @@ LL | LendingIterator::for_each(&(), f);
| required by a bound introduced by this call
|
help: this trait has no implementations, consider adding one
--> $DIR/hr_alias_normalization_leaking_vars.rs:24:1
--> $DIR/hr_alias_normalization_leaking_vars.rs:26:1
|
LL | trait LendingIterator {
| ^^^^^^^^^^^^^^^^^^^^^
error[E0308]: mismatched types
--> $DIR/hr_alias_normalization_leaking_vars.rs:32:36
--> $DIR/hr_alias_normalization_leaking_vars.rs:34:36
|
LL | LendingIterator::for_each(&(), f);
| ------------------------- ^ expected `Box<fn(...)>`, found fn item
@ -23,19 +23,19 @@ LL | LendingIterator::for_each(&(), f);
= note: expected struct `Box<for<'a> fn(<() as LendingIterator>::Item<'a>)>`
found fn item `fn(()) {f}`
note: method defined here
--> $DIR/hr_alias_normalization_leaking_vars.rs:26:8
--> $DIR/hr_alias_normalization_leaking_vars.rs:28:8
|
LL | fn for_each(&self, _f: Box<fn(Self::Item<'_>)>) {}
| ^^^^^^^^ ---------------------------
error[E0277]: the trait bound `(): LendingIterator` is not satisfied
--> $DIR/hr_alias_normalization_leaking_vars.rs:32:36
--> $DIR/hr_alias_normalization_leaking_vars.rs:34:36
|
LL | LendingIterator::for_each(&(), f);
| ^ the trait `LendingIterator` is not implemented for `()`
|
help: this trait has no implementations, consider adding one
--> $DIR/hr_alias_normalization_leaking_vars.rs:24:1
--> $DIR/hr_alias_normalization_leaking_vars.rs:26:1
|
LL | trait LendingIterator {
| ^^^^^^^^^^^^^^^^^^^^^

View file

@ -9,9 +9,7 @@ fn fndef_lub_leak_check() {
};
}
// These don't currently lub but could in theory one day.
// If that happens this test should be adjusted to use
// fn ptrs that can't be lub'd.
// Unused parameters on FnDefs are considered invariant
let lhs = foo::<for<'a> fn(&'static (), &'a ())>;
let rhs = foo::<for<'a> fn(&'a (), &'static ())>;

View file

@ -9,9 +9,7 @@ fn fndef_lub_leak_check() {
};
}
// These don't currently lub but could in theory one day.
// If that happens this test should be adjusted to use
// fn ptrs that can't be lub'd.
// Unused parameters on FnDefs are considered invariant
let lhs = foo::<for<'a> fn(&'static (), &'a ())>;
let rhs = foo::<for<'a> fn(&'a (), &'static ())>;

View file

@ -1,5 +1,5 @@
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
--> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:27:14
--> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:25:14
|
LL | unsafe { std::mem::transmute::<_, ()>(lubbed) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,70 +0,0 @@
//@ check-pass
//@ compile-flags: -Znext-solver
#![feature(type_alias_impl_trait)]
// Test that when lubbing two equal closure tys with different
// structural identities (i.e. `PartialEq::eq` on `ty::Ty` would be false)
// we don't coerce-lub to a fnptr.
//
// Most of this test is involved jank to be able to leak the hidden type
// of an opaque with a hidden type of `Closure<C1, C2>`. This then allows
// us to substitute `C1` and `C2` for arbitrary types in the parent scope.
//
// See: <https://github.com/lcnr/random-rust-snippets/issues/13>
struct WaddupGamers<T, U>(Option<T>, U);
impl<T: Leak<Unpin = U>, U> Unpin for WaddupGamers<T, U> {}
unsafe impl<T: Leak<Send = U>, U> Send for WaddupGamers<T, U> {}
pub trait Leak {
type Unpin;
type Send;
}
impl<T> Leak for (T,) {
type Unpin = T;
type Send = T;
}
fn define<C1, C2>() -> impl Sized {
WaddupGamers(None::<C1>, || ())
}
fn require_unpin<T: Unpin>(_: T) {}
fn require_send<T: Send>(_: T) {}
fn mk<T>() -> T { todo!() }
type NameMe<T> = impl Sized;
type NameMe2<T> = impl Sized;
#[define_opaque(NameMe, NameMe2)]
fn leak<T>()
where
T: Leak<Unpin = NameMe<T>, Send = NameMe2<T>>,
{
require_unpin(define::<T, for<'a> fn(&'a ())>());
require_send(define::<T, for<'a> fn(&'a ())>());
// This is the actual logic for lubbing two closures
// with syntactically different `ty::Ty`s:
// lhs: Closure<C1=T, C2=for<'a1> fn(&'a1 ())>
let lhs = mk::<NameMe<T>>();
// lhs: Closure<C1=T, C2=for<'a2> fn(&'a2 ())>
let rhs = mk::<NameMe2<T>>();
macro_rules! lub {
($lhs:expr, $rhs:expr) => {
if true { $lhs } else { $rhs }
};
}
// Lubbed to either:
// - `Closure<C1=T, C2=for<'a> fn(&'a ())>`
// - `fn(&())`
let lubbed = lub!(lhs, rhs);
// Use transmute to assert the size of `lubbed` is (), i.e.
// that it is a ZST closure type not a fnptr.
unsafe { std::mem::transmute::<_, ()>(lubbed) };
}
fn main() {}