Auto merge of #115538 - lcnr:fn-def-wf, r=compiler-errors
check `FnDef` return type for WF better version of #106807, fixes #84533 (mostly). It's not perfect given that we still ignore WF requirements involving bound regions but I wasn't able to quickly write an example, so even if theoretically exploitable, it should be far harder to trigger. This is strictly more restrictive than checking the return type for WF as part of the builtin `FnDef: FnOnce` impl (#106807) and moving to this approach in the future will not break any code. ~~It also agrees with my theoretical view of how this should behave~~ r? types
This commit is contained in:
commit
4c6c629866
8 changed files with 153 additions and 45 deletions
|
@ -647,6 +647,8 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
fn visit_ty(&mut self, t: <TyCtxt<'tcx> as ty::Interner>::Ty) -> Self::Result {
|
fn visit_ty(&mut self, t: <TyCtxt<'tcx> as ty::Interner>::Ty) -> Self::Result {
|
||||||
debug!("wf bounds for t={:?} t.kind={:#?}", t, t.kind());
|
debug!("wf bounds for t={:?} t.kind={:#?}", t, t.kind());
|
||||||
|
|
||||||
|
let tcx = self.tcx();
|
||||||
|
|
||||||
match *t.kind() {
|
match *t.kind() {
|
||||||
ty::Bool
|
ty::Bool
|
||||||
| ty::Char
|
| ty::Char
|
||||||
|
@ -707,6 +709,16 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::FnDef(did, args) => {
|
ty::FnDef(did, args) => {
|
||||||
|
// HACK: Check the return type of function definitions for
|
||||||
|
// well-formedness to mostly fix #84533. This is still not
|
||||||
|
// perfect and there may be ways to abuse the fact that we
|
||||||
|
// ignore requirements with escaping bound vars. That's a
|
||||||
|
// more general issue however.
|
||||||
|
//
|
||||||
|
// FIXME(eddyb) add the type to `walker` instead of recursing.
|
||||||
|
let fn_sig = tcx.fn_sig(did).instantiate(tcx, args);
|
||||||
|
fn_sig.output().skip_binder().visit_with(self);
|
||||||
|
|
||||||
let obligations = self.nominal_obligations(did, args);
|
let obligations = self.nominal_obligations(did, args);
|
||||||
self.out.extend(obligations);
|
self.out.extend(obligations);
|
||||||
}
|
}
|
||||||
|
@ -716,7 +728,7 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
if !r.has_escaping_bound_vars() && !rty.has_escaping_bound_vars() {
|
if !r.has_escaping_bound_vars() && !rty.has_escaping_bound_vars() {
|
||||||
let cause = self.cause(traits::ReferenceOutlivesReferent(t));
|
let cause = self.cause(traits::ReferenceOutlivesReferent(t));
|
||||||
self.out.push(traits::Obligation::with_depth(
|
self.out.push(traits::Obligation::with_depth(
|
||||||
self.tcx(),
|
tcx,
|
||||||
cause,
|
cause,
|
||||||
self.recursion_depth,
|
self.recursion_depth,
|
||||||
self.param_env,
|
self.param_env,
|
||||||
|
@ -805,12 +817,12 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
// obligations that don't refer to Self and
|
// obligations that don't refer to Self and
|
||||||
// checking those
|
// checking those
|
||||||
|
|
||||||
let defer_to_coercion = self.tcx().features().object_safe_for_dispatch;
|
let defer_to_coercion = tcx.features().object_safe_for_dispatch;
|
||||||
|
|
||||||
if !defer_to_coercion {
|
if !defer_to_coercion {
|
||||||
if let Some(principal) = data.principal_def_id() {
|
if let Some(principal) = data.principal_def_id() {
|
||||||
self.out.push(traits::Obligation::with_depth(
|
self.out.push(traits::Obligation::with_depth(
|
||||||
self.tcx(),
|
tcx,
|
||||||
self.cause(traits::WellFormed(None)),
|
self.cause(traits::WellFormed(None)),
|
||||||
self.recursion_depth,
|
self.recursion_depth,
|
||||||
self.param_env,
|
self.param_env,
|
||||||
|
@ -835,7 +847,7 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
ty::Infer(_) => {
|
ty::Infer(_) => {
|
||||||
let cause = self.cause(traits::WellFormed(None));
|
let cause = self.cause(traits::WellFormed(None));
|
||||||
self.out.push(traits::Obligation::with_depth(
|
self.out.push(traits::Obligation::with_depth(
|
||||||
self.tcx(),
|
tcx,
|
||||||
cause,
|
cause,
|
||||||
self.recursion_depth,
|
self.recursion_depth,
|
||||||
self.param_env,
|
self.param_env,
|
||||||
|
@ -850,6 +862,8 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_const(&mut self, c: <TyCtxt<'tcx> as ty::Interner>::Const) -> Self::Result {
|
fn visit_const(&mut self, c: <TyCtxt<'tcx> as ty::Interner>::Const) -> Self::Result {
|
||||||
|
let tcx = self.tcx();
|
||||||
|
|
||||||
match c.kind() {
|
match c.kind() {
|
||||||
ty::ConstKind::Unevaluated(uv) => {
|
ty::ConstKind::Unevaluated(uv) => {
|
||||||
if !c.has_escaping_bound_vars() {
|
if !c.has_escaping_bound_vars() {
|
||||||
|
@ -861,7 +875,7 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
));
|
));
|
||||||
let cause = self.cause(traits::WellFormed(None));
|
let cause = self.cause(traits::WellFormed(None));
|
||||||
self.out.push(traits::Obligation::with_depth(
|
self.out.push(traits::Obligation::with_depth(
|
||||||
self.tcx(),
|
tcx,
|
||||||
cause,
|
cause,
|
||||||
self.recursion_depth,
|
self.recursion_depth,
|
||||||
self.param_env,
|
self.param_env,
|
||||||
|
@ -873,7 +887,7 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
let cause = self.cause(traits::WellFormed(None));
|
let cause = self.cause(traits::WellFormed(None));
|
||||||
|
|
||||||
self.out.push(traits::Obligation::with_depth(
|
self.out.push(traits::Obligation::with_depth(
|
||||||
self.tcx(),
|
tcx,
|
||||||
cause,
|
cause,
|
||||||
self.recursion_depth,
|
self.recursion_depth,
|
||||||
self.param_env,
|
self.param_env,
|
||||||
|
@ -895,7 +909,7 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
|
||||||
));
|
));
|
||||||
let cause = self.cause(traits::WellFormed(None));
|
let cause = self.cause(traits::WellFormed(None));
|
||||||
self.out.push(traits::Obligation::with_depth(
|
self.out.push(traits::Obligation::with_depth(
|
||||||
self.tcx(),
|
tcx,
|
||||||
cause,
|
cause,
|
||||||
self.recursion_depth,
|
self.recursion_depth,
|
||||||
self.param_env,
|
self.param_env,
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
//@ check-pass
|
|
||||||
//@ known-bug: #84533
|
|
||||||
|
|
||||||
// Should fail. Lifetimes are checked correctly when `foo` is called, but NOT
|
|
||||||
// when only the lifetime parameters are instantiated.
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn foo<'b, 'a>() -> PhantomData<&'b &'a ()> {
|
|
||||||
PhantomData
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[allow(path_statements)]
|
|
||||||
fn caller<'b, 'a>() {
|
|
||||||
foo::<'b, 'a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In contrast to above, below code correctly does NOT compile.
|
|
||||||
// fn caller<'b, 'a>() {
|
|
||||||
// foo::<'b, 'a>();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// error: lifetime may not live long enough
|
|
||||||
// --> src/main.rs:22:5
|
|
||||||
// |
|
|
||||||
// 21 | fn caller<'b, 'a>() {
|
|
||||||
// | -- -- lifetime `'a` defined here
|
|
||||||
// | |
|
|
||||||
// | lifetime `'b` defined here
|
|
||||||
// 22 | foo::<'b, 'a>();
|
|
||||||
// | ^^^^^^^^^^^^^^^ requires that `'a` must outlive `'b`
|
|
||||||
// |
|
|
||||||
// = help: consider adding the following bound: `'a: 'b`
|
|
||||||
|
|
||||||
fn main() {}
|
|
|
@ -15,4 +15,5 @@ pub fn uwu() -> <() as Project>::Assoc {}
|
||||||
//~^ ERROR the trait bound `(): Project` is not satisfied
|
//~^ ERROR the trait bound `(): Project` is not satisfied
|
||||||
//~| ERROR the trait bound `(): Project` is not satisfied
|
//~| ERROR the trait bound `(): Project` is not satisfied
|
||||||
//~| ERROR the trait bound `(): Project` is not satisfied
|
//~| ERROR the trait bound `(): Project` is not satisfied
|
||||||
|
//~| ERROR the trait bound `(): Project` is not satisfied
|
||||||
//~| ERROR function is expected to take 1 argument, but it takes 0 arguments
|
//~| ERROR function is expected to take 1 argument, but it takes 0 arguments
|
||||||
|
|
|
@ -35,6 +35,18 @@ help: this trait has no implementations, consider adding one
|
||||||
LL | trait Project {
|
LL | trait Project {
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `(): Project` is not satisfied
|
||||||
|
--> $DIR/bad-projection.rs:14:1
|
||||||
|
|
|
||||||
|
LL | pub fn uwu() -> <() as Project>::Assoc {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Project` is not implemented for `()`
|
||||||
|
|
|
||||||
|
help: this trait has no implementations, consider adding one
|
||||||
|
--> $DIR/bad-projection.rs:9:1
|
||||||
|
|
|
||||||
|
LL | trait Project {
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
error[E0277]: the trait bound `(): Project` is not satisfied
|
error[E0277]: the trait bound `(): Project` is not satisfied
|
||||||
--> $DIR/bad-projection.rs:14:40
|
--> $DIR/bad-projection.rs:14:40
|
||||||
|
|
|
|
||||||
|
@ -47,7 +59,7 @@ help: this trait has no implementations, consider adding one
|
||||||
LL | trait Project {
|
LL | trait Project {
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to 4 previous errors
|
error: aborting due to 5 previous errors
|
||||||
|
|
||||||
Some errors have detailed explanations: E0277, E0593.
|
Some errors have detailed explanations: E0277, E0593.
|
||||||
For more information about an error, try `rustc --explain E0277`.
|
For more information about an error, try `rustc --explain E0277`.
|
||||||
|
|
44
tests/ui/wf/wf-fn-def-check-sig-1.rs
Normal file
44
tests/ui/wf/wf-fn-def-check-sig-1.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Regression test for #84533.
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
fn foo<'b, 'a>() -> PhantomData<&'b &'a ()> {
|
||||||
|
PhantomData
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
|
||||||
|
let f = foo::<'b, 'a>;
|
||||||
|
f.baz(x)
|
||||||
|
//~^ ERROR lifetime may not live long enough
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Foo<'a, 'b, T: ?Sized> {
|
||||||
|
fn baz(self, s: &'a T) -> &'b T;
|
||||||
|
}
|
||||||
|
impl<'a, 'b, R, F, T: ?Sized> Foo<'a, 'b, T> for F
|
||||||
|
where
|
||||||
|
F: Fn() -> R,
|
||||||
|
R: ProofForConversion<'a, 'b, T>,
|
||||||
|
{
|
||||||
|
fn baz(self, s: &'a T) -> &'b T {
|
||||||
|
self().convert(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ProofForConversion<'a, 'b, T: ?Sized> {
|
||||||
|
fn convert(self, s: &'a T) -> &'b T;
|
||||||
|
}
|
||||||
|
impl<'a, 'b, T: ?Sized> ProofForConversion<'a, 'b, T> for PhantomData<&'b &'a ()> {
|
||||||
|
fn convert(self, s: &'a T) -> &'b T {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let d;
|
||||||
|
{
|
||||||
|
let x = "Hello World".to_string();
|
||||||
|
d = extend_lifetime(&x);
|
||||||
|
}
|
||||||
|
println!("{}", d);
|
||||||
|
}
|
15
tests/ui/wf/wf-fn-def-check-sig-1.stderr
Normal file
15
tests/ui/wf/wf-fn-def-check-sig-1.stderr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
error: lifetime may not live long enough
|
||||||
|
--> $DIR/wf-fn-def-check-sig-1.rs:11:5
|
||||||
|
|
|
||||||
|
LL | fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
|
||||||
|
| -- -- lifetime `'b` defined here
|
||||||
|
| |
|
||||||
|
| lifetime `'a` defined here
|
||||||
|
LL | let f = foo::<'b, 'a>;
|
||||||
|
LL | f.baz(x)
|
||||||
|
| ^^^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
||||||
|
|
|
||||||
|
= help: consider adding the following bound: `'a: 'b`
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
44
tests/ui/wf/wf-fn-def-check-sig-2.rs
Normal file
44
tests/ui/wf/wf-fn-def-check-sig-2.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Regression test for #84533 involving higher-ranked regions
|
||||||
|
// in the return type.
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
fn foo<'c, 'b, 'a>(_: &'c ()) -> (&'c (), PhantomData<&'b &'a ()>) {
|
||||||
|
(&(), PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
|
||||||
|
let f = foo;
|
||||||
|
f.baz(x)
|
||||||
|
//~^ ERROR lifetime may not live long enough
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Foo<'a, 'b, T: ?Sized> {
|
||||||
|
fn baz(self, s: &'a T) -> &'b T;
|
||||||
|
}
|
||||||
|
impl<'a, 'b, R, F, T: ?Sized> Foo<'a, 'b, T> for F
|
||||||
|
where
|
||||||
|
F: for<'c> Fn(&'c ()) -> (&'c (), R),
|
||||||
|
R: ProofForConversion<'a, 'b, T>,
|
||||||
|
{
|
||||||
|
fn baz(self, s: &'a T) -> &'b T {
|
||||||
|
self(&()).1.convert(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ProofForConversion<'a, 'b, T: ?Sized> {
|
||||||
|
fn convert(self, s: &'a T) -> &'b T;
|
||||||
|
}
|
||||||
|
impl<'a, 'b, T: ?Sized> ProofForConversion<'a, 'b, T> for PhantomData<&'b &'a ()> {
|
||||||
|
fn convert(self, s: &'a T) -> &'b T {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let d;
|
||||||
|
{
|
||||||
|
let x = "Hello World".to_string();
|
||||||
|
d = extend_lifetime(&x);
|
||||||
|
}
|
||||||
|
println!("{}", d);
|
||||||
|
}
|
15
tests/ui/wf/wf-fn-def-check-sig-2.stderr
Normal file
15
tests/ui/wf/wf-fn-def-check-sig-2.stderr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
error: lifetime may not live long enough
|
||||||
|
--> $DIR/wf-fn-def-check-sig-2.rs:11:5
|
||||||
|
|
|
||||||
|
LL | fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
|
||||||
|
| -- -- lifetime `'b` defined here
|
||||||
|
| |
|
||||||
|
| lifetime `'a` defined here
|
||||||
|
LL | let f = foo;
|
||||||
|
LL | f.baz(x)
|
||||||
|
| ^^^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
||||||
|
|
|
||||||
|
= help: consider adding the following bound: `'a: 'b`
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue