Skip to content

Commit 042b530

Browse files
committed
Properly handle closure<->closure and fndef<->fndef coerce-lubs
- leak checking the lub for fndef<->fndef coerce-lubs - start lubbing closure<->closure coerce-lubs and leak check it
1 parent a6462ac commit 042b530

File tree

6 files changed

+193
-37
lines changed

6 files changed

+193
-37
lines changed

compiler/rustc_hir_typeck/src/coercion.rs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,8 +1163,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11631163
exprs.len()
11641164
);
11651165

1166-
// The following check fixes #88097, where the compiler erroneously
1167-
// attempted to coerce a closure type to itself via a function pointer.
1166+
// Fast Path: don't go through the coercion logic if we're coercing
1167+
// a type to itself. This is unfortunately quite perf relevant so
1168+
// we do it even though it may mask bugs in the coercion logic.
11681169
if prev_ty == new_ty {
11691170
return Ok(prev_ty);
11701171
}
@@ -1193,34 +1194,51 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11931194
if is_capturing_closure(prev_ty) || is_capturing_closure(new_ty) {
11941195
(None, None)
11951196
} else {
1196-
match (prev_ty.kind(), new_ty.kind()) {
1197-
(ty::FnDef(..), ty::FnDef(..)) => {
1198-
// Don't reify if the function types have a LUB, i.e., they
1199-
// are the same function and their parameters have a LUB.
1200-
match self.commit_if_ok(|_| {
1201-
// We need to eagerly handle nested obligations due to lazy norm.
1202-
if self.next_trait_solver() {
1203-
let ocx = ObligationCtxt::new(self);
1204-
let value = ocx.lub(cause, self.param_env, prev_ty, new_ty)?;
1205-
if ocx.try_evaluate_obligations().is_empty() {
1206-
Ok(InferOk {
1207-
value,
1208-
obligations: ocx.into_pending_obligations(),
1209-
})
1210-
} else {
1211-
Err(TypeError::Mismatch)
1212-
}
1197+
let lubbed_tys = || {
1198+
self.commit_if_ok(|snapshot| {
1199+
let outer_universe = self.infcx.universe();
1200+
1201+
// We need to eagerly handle nested obligations due to lazy norm.
1202+
let result = if self.next_trait_solver() {
1203+
let ocx = ObligationCtxt::new(self);
1204+
let value = ocx.lub(cause, self.param_env, prev_ty, new_ty)?;
1205+
if ocx.try_evaluate_obligations().is_empty() {
1206+
Ok(InferOk { value, obligations: ocx.into_pending_obligations() })
12131207
} else {
1214-
self.at(cause, self.param_env).lub(prev_ty, new_ty)
1215-
}
1216-
}) {
1217-
// We have a LUB of prev_ty and new_ty, just return it.
1218-
Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)),
1219-
Err(_) => {
1220-
(Some(prev_ty.fn_sig(self.tcx)), Some(new_ty.fn_sig(self.tcx)))
1208+
Err(TypeError::Mismatch)
12211209
}
1210+
} else {
1211+
self.at(cause, self.param_env).lub(prev_ty, new_ty)
1212+
};
1213+
1214+
self.leak_check(outer_universe, Some(snapshot))?;
1215+
result
1216+
})
1217+
};
1218+
1219+
match (prev_ty.kind(), new_ty.kind()) {
1220+
// Don't coerce pairs of fndefs or pairs of closures to fn ptrs
1221+
// if they can just be lubbed.
1222+
//
1223+
// See #88097 or `lub_closures_before_fnptr_coercion.rs` for where
1224+
// we would erroneously coerce closures to fnptrs when attempting to
1225+
// coerce a closure to itself.
1226+
(ty::FnDef(..), ty::FnDef(..)) => match lubbed_tys() {
1227+
Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)),
1228+
Err(_) => (Some(prev_ty.fn_sig(self.tcx)), Some(new_ty.fn_sig(self.tcx))),
1229+
},
1230+
(ty::Closure(_, args_a), ty::Closure(_, args_b)) => match lubbed_tys() {
1231+
Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)),
1232+
Err(_) => {
1233+
let a_sig = self
1234+
.tcx
1235+
.signature_unclosure(args_a.as_closure().sig(), hir::Safety::Safe);
1236+
let b_sig = self
1237+
.tcx
1238+
.signature_unclosure(args_b.as_closure().sig(), hir::Safety::Safe);
1239+
(Some(a_sig), Some(b_sig))
12221240
}
1223-
}
1241+
},
12241242
(ty::Closure(_, args), ty::FnDef(..)) => {
12251243
let b_sig = new_ty.fn_sig(self.tcx);
12261244
let a_sig =
@@ -1233,16 +1251,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12331251
self.tcx.signature_unclosure(args.as_closure().sig(), a_sig.safety());
12341252
(Some(a_sig), Some(b_sig))
12351253
}
1236-
(ty::Closure(_, args_a), ty::Closure(_, args_b)) => (
1237-
Some(
1238-
self.tcx
1239-
.signature_unclosure(args_a.as_closure().sig(), hir::Safety::Safe),
1240-
),
1241-
Some(
1242-
self.tcx
1243-
.signature_unclosure(args_b.as_closure().sig(), hir::Safety::Safe),
1244-
),
1245-
),
1254+
// ty::FnPtr x ty::FnPtr is fine to just be handled through a normal `unify`
1255+
// call using `lub` which is what will happen on the normal path.
12461256
_ => (None, None),
12471257
}
12481258
}

tests/ui/coercion/issue-88097.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// behavior has been fixed.
44

55
//@ check-pass
6+
//@ revisions: current next
7+
//@ ignore-compare-mode-next-solver (explicit revisions)
8+
//@[next] compile-flags: -Znext-solver
69

710
fn peculiar() -> impl Fn(u8) -> u8 {
811
return |x| x + 1
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@ check-pass
2+
3+
fn foo<T>() {}
4+
5+
fn fndef_lub_leak_check() {
6+
macro_rules! lub {
7+
($lhs:expr, $rhs:expr) => {
8+
if true { $lhs } else { $rhs }
9+
};
10+
}
11+
12+
// These don't currently lub but could in theory one day.
13+
// If that happens this test should be adjusted to use
14+
// fn ptrs that can't be lub'd.
15+
let lhs = foo::<for<'a> fn(&'static (), &'a ())>;
16+
let rhs = foo::<for<'a> fn(&'a (), &'static ())>;
17+
18+
// If we leak check then we know we should coerce these
19+
// to `fn()`, if we don't leak check we may try to keep
20+
// them as `FnDef`s which would result in a borrowck
21+
// error.
22+
let lubbed = lub!(lhs, rhs);
23+
24+
// assert that we coerced lhs/rhs to a fn ptr
25+
is_fnptr(lubbed);
26+
}
27+
28+
trait FnPtr {}
29+
impl FnPtr for fn() {}
30+
fn is_fnptr<T: FnPtr>(_: T) {}
31+
32+
fn main() {}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
fn foo<T>() {}
2+
3+
fn fndef_lub_leak_check() {
4+
macro_rules! lub {
5+
($lhs:expr, $rhs:expr) => {
6+
if true { $lhs } else { $rhs }
7+
};
8+
}
9+
10+
// These don't currently lub but could in theory one day.
11+
// If that happens this test should be adjusted to use
12+
// fn ptrs that can't be lub'd.
13+
let lhs = foo::<for<'a> fn(&'static (), &'a ())>;
14+
let rhs = foo::<for<'a> fn(&'a (), &'static ())>;
15+
16+
loop {}
17+
18+
// If we leak check then we know we should coerce these
19+
// to `fn()`, if we don't leak check we may try to keep
20+
// them as `FnDef`s which would cause this code to compile
21+
// as borrowck won't emit errors for deadcode.
22+
let lubbed = lub!(lhs, rhs);
23+
24+
// assert that `lubbed` is a ZST/`FnDef`
25+
unsafe { std::mem::transmute::<_, ()>(lubbed) }
26+
//~^ ERROR: cannot transmute between types of different sizes
27+
}
28+
29+
fn main() {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
--> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:25:14
3+
|
4+
LL | unsafe { std::mem::transmute::<_, ()>(lubbed) }
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `fn()` (64 bits)
8+
= note: target type: `()` (0 bits)
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0512`.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//@ check-pass
2+
//@ compile-flags: -Znext-solver
3+
4+
#![feature(type_alias_impl_trait)]
5+
6+
// Test that when lubbing two equal closure tys with different
7+
// structural identities (i.e. `PartialEq::eq` on `ty::Ty` would be false)
8+
// we don't coerce-lub to a fnptr.
9+
//
10+
// Most of this test is involved jank to be able to leak the hidden type
11+
// of an opaque with a hidden type of `Closure<C1, C2>`. This then allows
12+
// us to substitute `C1` and `C2` for arbitrary types in the parent scope.
13+
//
14+
// See: <https://github.com/lcnr/random-rust-snippets/issues/13>
15+
16+
struct WaddupGamers<T, U>(Option<T>, U);
17+
impl<T: Leak<Unpin = U>, U> Unpin for WaddupGamers<T, U> {}
18+
unsafe impl<T: Leak<Send = U>, U> Send for WaddupGamers<T, U> {}
19+
pub trait Leak {
20+
type Unpin;
21+
type Send;
22+
}
23+
impl<T> Leak for (T,) {
24+
type Unpin = T;
25+
type Send = T;
26+
}
27+
fn define<C1, C2>() -> impl Sized {
28+
WaddupGamers(None::<C1>, || ())
29+
}
30+
31+
fn require_unpin<T: Unpin>(_: T) {}
32+
fn require_send<T: Send>(_: T) {}
33+
fn mk<T>() -> T { todo!() }
34+
35+
type NameMe<T> = impl Sized;
36+
type NameMe2<T> = impl Sized;
37+
38+
#[define_opaque(NameMe, NameMe2)]
39+
fn leak<T>()
40+
where
41+
T: Leak<Unpin = NameMe<T>, Send = NameMe2<T>>,
42+
{
43+
require_unpin(define::<T, for<'a> fn(&'a ())>());
44+
require_send(define::<T, for<'a> fn(&'a ())>());
45+
46+
// This is the actual logic for lubbing two closures
47+
// with syntactically different `ty::Ty`s:
48+
49+
// lhs: Closure<C1=T, C2=for<'a1> fn(&'a1 ())>
50+
let lhs = mk::<NameMe<T>>();
51+
// lhs: Closure<C1=T, C2=for<'a2> fn(&'a2 ())>
52+
let rhs = mk::<NameMe2<T>>();
53+
54+
macro_rules! lub {
55+
($lhs:expr, $rhs:expr) => {
56+
if true { $lhs } else { $rhs }
57+
};
58+
}
59+
60+
// Lubbed to either:
61+
// - `Closure<C1=T, C2=for<'a> fn(&'a ())>`
62+
// - `fn(&())`
63+
let lubbed = lub!(lhs, rhs);
64+
65+
// Use transmute to assert the size of `lubbed` is (), i.e.
66+
// that it is a ZST closure type not a fnptr.
67+
unsafe { std::mem::transmute::<_, ()>(lubbed) };
68+
}
69+
70+
fn main() {}

0 commit comments

Comments
 (0)