Skip to content

Commit e8472e8

Browse files
Check unnormalized signature on pointer cast
1 parent eb33b43 commit e8472e8

7 files changed

+127
-13
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

+64-7
Original file line numberDiff line numberDiff line change
@@ -1979,19 +1979,76 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
19791979

19801980
match cast_kind {
19811981
CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer) => {
1982-
let fn_sig = op.ty(body, tcx).fn_sig(tcx);
1982+
let src_sig = op.ty(body, tcx).fn_sig(tcx);
1983+
1984+
// HACK: This shouldn't be necessary... We can remove this when we actually
1985+
// get binders with where clauses, then elaborate implied bounds into that
1986+
// binder, and implement a higher-ranked subtyping algorithm that actually
1987+
// respects these implied bounds.
1988+
//
1989+
// This protects against the case where we are casting from a higher-ranked
1990+
// fn item to a non-higher-ranked fn pointer, where the cast throws away
1991+
// implied bounds that would've needed to be checked at the call site. This
1992+
// only works when we're casting to a non-higher-ranked fn ptr, since
1993+
// placeholders in the target signature could have untracked implied
1994+
// bounds, resulting in incorrect errors.
1995+
//
1996+
// We check that this signature is WF before subtyping the signature with
1997+
// the target fn sig.
1998+
if src_sig.has_bound_regions()
1999+
&& let ty::FnPtr(target_fn_tys, target_hdr) = *ty.kind()
2000+
&& let target_sig = target_fn_tys.with(target_hdr)
2001+
&& let Some(target_sig) = target_sig.no_bound_vars()
2002+
{
2003+
let src_sig = self.infcx.instantiate_binder_with_fresh_vars(
2004+
span,
2005+
BoundRegionConversionTime::HigherRankedType,
2006+
src_sig,
2007+
);
2008+
let src_ty = Ty::new_fn_ptr(self.tcx(), ty::Binder::dummy(src_sig));
2009+
self.prove_predicate(
2010+
ty::ClauseKind::WellFormed(src_ty.into()),
2011+
location.to_locations(),
2012+
ConstraintCategory::Cast { unsize_to: None },
2013+
);
2014+
2015+
let src_ty = self.normalize(src_ty, location);
2016+
if let Err(terr) = self.sub_types(
2017+
src_ty,
2018+
*ty,
2019+
location.to_locations(),
2020+
ConstraintCategory::Cast { unsize_to: None },
2021+
) {
2022+
span_mirbug!(
2023+
self,
2024+
rvalue,
2025+
"equating {:?} with {:?} yields {:?}",
2026+
target_sig,
2027+
src_sig,
2028+
terr
2029+
);
2030+
};
2031+
}
2032+
2033+
let src_ty = Ty::new_fn_ptr(tcx, src_sig);
2034+
// HACK: We want to assert that the signature of the source fn is
2035+
// well-formed, because we don't enforce that via the WF of FnDef
2036+
// types normally. This should be removed when we improve the tracking
2037+
// of implied bounds of fn signatures.
2038+
self.prove_predicate(
2039+
ty::ClauseKind::WellFormed(src_ty.into()),
2040+
location.to_locations(),
2041+
ConstraintCategory::Cast { unsize_to: None },
2042+
);
19832043

19842044
// The type that we see in the fcx is like
19852045
// `foo::<'a, 'b>`, where `foo` is the path to a
19862046
// function definition. When we extract the
19872047
// signature, it comes from the `fn_sig` query,
19882048
// and hence may contain unnormalized results.
1989-
let fn_sig = self.normalize(fn_sig, location);
1990-
1991-
let ty_fn_ptr_from = Ty::new_fn_ptr(tcx, fn_sig);
1992-
2049+
let src_ty = self.normalize(src_ty, location);
19932050
if let Err(terr) = self.sub_types(
1994-
ty_fn_ptr_from,
2051+
src_ty,
19952052
*ty,
19962053
location.to_locations(),
19972054
ConstraintCategory::Cast { unsize_to: None },
@@ -2000,7 +2057,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
20002057
self,
20012058
rvalue,
20022059
"equating {:?} with {:?} yields {:?}",
2003-
ty_fn_ptr_from,
2060+
src_ty,
20042061
ty,
20052062
terr
20062063
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
static UNIT: &'static &'static () = &&();
2+
3+
fn foo<'a: 'a, 'b: 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }
4+
5+
fn bad<'a, T>(x: &'a T) -> &'static T {
6+
let f: fn(_, &'a T) -> &'static T = foo;
7+
//~^ ERROR lifetime may not live long enough
8+
f(UNIT, x)
9+
}
10+
11+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/implied-bounds-on-nested-references-plus-variance-early-bound.rs:6:12
3+
|
4+
LL | fn bad<'a, T>(x: &'a T) -> &'static T {
5+
| -- lifetime `'a` defined here
6+
LL | let f: fn(_, &'a T) -> &'static T = foo;
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
8+
9+
error: aborting due to 1 previous error
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
trait ToArg<T> {
2+
type Arg;
3+
}
4+
impl<T, U> ToArg<T> for U {
5+
type Arg = T;
6+
}
7+
8+
fn extend_inner<'a, 'b>(x: &'a str) -> <&'b &'a () as ToArg<&'b str>>::Arg { x }
9+
fn extend<'a, 'b>(x: &'a str) -> &'b str {
10+
(extend_inner as fn(_) -> _)(x)
11+
//~^ ERROR lifetime may not live long enough
12+
}
13+
14+
fn main() {
15+
let y = extend(&String::from("Hello World"));
16+
println!("{}", y);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/implied-bounds-on-nested-references-plus-variance-unnormalized.rs:10:5
3+
|
4+
LL | fn extend<'a, 'b>(x: &'a str) -> &'b str {
5+
| -- -- lifetime `'b` defined here
6+
| |
7+
| lifetime `'a` defined here
8+
LL | (extend_inner as fn(_) -> _)(x)
9+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
10+
|
11+
= help: consider adding the following bound: `'a: 'b`
12+
13+
error: aborting due to 1 previous error
14+

tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
//@ check-pass
2-
//@ known-bug: #25860
3-
4-
// Should fail. The combination of variance and implied bounds for nested
5-
// references allows us to infer a longer lifetime than we can prove.
6-
71
static UNIT: &'static &'static () = &&();
82

93
fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }
104

115
fn bad<'a, T>(x: &'a T) -> &'static T {
126
let f: fn(_, &'a T) -> &'static T = foo;
7+
//~^ ERROR lifetime may not live long enough
138
f(UNIT, x)
149
}
1510

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/implied-bounds-on-nested-references-plus-variance.rs:6:12
3+
|
4+
LL | fn bad<'a, T>(x: &'a T) -> &'static T {
5+
| -- lifetime `'a` defined here
6+
LL | let f: fn(_, &'a T) -> &'static T = foo;
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
8+
9+
error: aborting due to 1 previous error
10+

0 commit comments

Comments
 (0)