Rollup merge of #118000 - compiler-errors:placeholder-ty-outlives, r=aliemjay

Make regionck care about placeholders in outlives components

Currently, we don't consider a placeholder type `!T` to be a type component when it comes to processing type-outlives obligations. This means that they are essentially treated like unit values with no sub-components, and always outlive any region. This is problematic for `non_lifetime_binders`, and even more problematic for `with_negative_coherence`, since negative coherence uses placeholders as universals.

This PR adds `Component::Placeholder` which acts much like `Component::Param`. This currently causes a regression in some non-lifetime-binders tests because `for<T> T: 'static` doesn't imply itself when processing outlives obligations, so code like this will fail:

```
fn foo() where for<T> T: 'static {
  foo() //~ fails
}
```

Since the where clause doesn't imply itself. This requires making the `MatchAgainstHigherRankedOutlives` relation smarter when it comes to binders.

r? types
This commit is contained in:
Michael Goulet 2023-11-19 19:14:35 -08:00 committed by GitHub
commit 6388c0ef04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 177 additions and 13 deletions

View File

@ -80,6 +80,10 @@ pub(crate) fn insert_outlives_predicate<'tcx>(
.or_insert(span);
}
Component::Placeholder(_) => {
span_bug!(span, "Should not deduce placeholder outlives component");
}
Component::Alias(alias_ty) => {
// This would either arise from something like:
//

View File

@ -2351,6 +2351,7 @@ pub fn construct_generic_bound_failure(
let labeled_user_string = match bound_kind {
GenericKind::Param(ref p) => format!("the parameter type `{p}`"),
GenericKind::Placeholder(ref p) => format!("the placeholder type `{p:?}`"),
GenericKind::Alias(ref p) => match p.kind(self.tcx) {
ty::AliasKind::Projection | ty::AliasKind::Inherent => {
format!("the associated type `{p}`")

View File

@ -11,6 +11,7 @@
pub enum Component<'tcx> {
Region(ty::Region<'tcx>),
Param(ty::ParamTy),
Placeholder(ty::PlaceholderType),
UnresolvedInferenceVariable(ty::InferTy),
// Projections like `T::Foo` are tricky because a constraint like
@ -120,6 +121,10 @@ fn compute_components<'tcx>(
out.push(Component::Param(p));
}
ty::Placeholder(p) => {
out.push(Component::Placeholder(p));
}
// For projections, we prefer to generate an obligation like
// `<P0 as Trait<P1...Pn>>::Foo: 'a`, because this gives the
// regionck more ways to prove that it holds. However,
@ -176,7 +181,6 @@ fn compute_components<'tcx>(
ty::Tuple(..) | // ...
ty::FnPtr(_) | // OutlivesFunction (*)
ty::Dynamic(..) | // OutlivesObject, OutlivesFragment (*)
ty::Placeholder(..) |
ty::Bound(..) |
ty::Error(_) => {
// (*) Function pointers and trait objects are both binders.

View File

@ -243,6 +243,9 @@ fn components_must_outlive(
Component::Param(param_ty) => {
self.param_ty_must_outlive(origin, region, *param_ty);
}
Component::Placeholder(placeholder_ty) => {
self.placeholder_ty_must_outlive(origin, region, *placeholder_ty);
}
Component::Alias(alias_ty) => self.alias_ty_must_outlive(origin, region, *alias_ty),
Component::EscapingAlias(subcomponents) => {
self.components_must_outlive(origin, &subcomponents, region, category);
@ -267,10 +270,28 @@ fn param_ty_must_outlive(
region: ty::Region<'tcx>,
param_ty: ty::ParamTy,
) {
let verify_bound = self.verify_bound.param_bound(param_ty);
let verify_bound = self.verify_bound.param_or_placeholder_bound(param_ty.to_ty(self.tcx));
self.delegate.push_verify(origin, GenericKind::Param(param_ty), region, verify_bound);
}
#[instrument(level = "debug", skip(self))]
fn placeholder_ty_must_outlive(
&mut self,
origin: infer::SubregionOrigin<'tcx>,
region: ty::Region<'tcx>,
placeholder_ty: ty::PlaceholderType,
) {
let verify_bound = self
.verify_bound
.param_or_placeholder_bound(Ty::new_placeholder(self.tcx, placeholder_ty));
self.delegate.push_verify(
origin,
GenericKind::Placeholder(placeholder_ty),
region,
verify_bound,
);
}
#[instrument(level = "debug", skip(self))]
fn alias_ty_must_outlive(
&mut self,

View File

@ -37,11 +37,11 @@ pub fn new(
}
#[instrument(level = "debug", skip(self))]
pub fn param_bound(&self, param_ty: ty::ParamTy) -> VerifyBound<'tcx> {
pub fn param_or_placeholder_bound(&self, ty: Ty<'tcx>) -> VerifyBound<'tcx> {
// Start with anything like `T: 'a` we can scrape from the
// environment. If the environment contains something like
// `for<'a> T: 'a`, then we know that `T` outlives everything.
let declared_bounds_from_env = self.declared_generic_bounds_from_env(param_ty);
let declared_bounds_from_env = self.declared_generic_bounds_from_env(ty);
debug!(?declared_bounds_from_env);
let mut param_bounds = vec![];
for declared_bound in declared_bounds_from_env {
@ -51,7 +51,7 @@ pub fn param_bound(&self, param_ty: ty::ParamTy) -> VerifyBound<'tcx> {
param_bounds.push(VerifyBound::OutlivedBy(region));
} else {
// This is `for<'a> T: 'a`. This means that `T` outlives everything! All done here.
debug!("found that {param_ty:?} outlives any lifetime, returning empty vector");
debug!("found that {ty:?} outlives any lifetime, returning empty vector");
return VerifyBound::AllBounds(vec![]);
}
}
@ -168,7 +168,10 @@ fn bound_from_single_component(
) -> VerifyBound<'tcx> {
match *component {
Component::Region(lt) => VerifyBound::OutlivedBy(lt),
Component::Param(param_ty) => self.param_bound(param_ty),
Component::Param(param_ty) => self.param_or_placeholder_bound(param_ty.to_ty(self.tcx)),
Component::Placeholder(placeholder_ty) => {
self.param_or_placeholder_bound(Ty::new_placeholder(self.tcx, placeholder_ty))
}
Component::Alias(alias_ty) => self.alias_bound(alias_ty, visited),
Component::EscapingAlias(ref components) => {
self.bound_from_components(components, visited)
@ -195,9 +198,9 @@ fn bound_from_single_component(
/// bounds, but all the bounds it returns can be relied upon.
fn declared_generic_bounds_from_env(
&self,
param_ty: ty::ParamTy,
generic_ty: Ty<'tcx>,
) -> Vec<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
let generic_ty = param_ty.to_ty(self.tcx);
assert!(matches!(generic_ty.kind(), ty::Param(_) | ty::Placeholder(_)));
self.declared_generic_bounds_from_env_for_erased_ty(generic_ty)
}

View File

@ -147,6 +147,7 @@ pub struct Verify<'tcx> {
#[derive(Copy, Clone, PartialEq, Eq, Hash, TypeFoldable, TypeVisitable)]
pub enum GenericKind<'tcx> {
Param(ty::ParamTy),
Placeholder(ty::PlaceholderType),
Alias(ty::AliasTy<'tcx>),
}
@ -707,6 +708,7 @@ impl<'tcx> fmt::Debug for GenericKind<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
GenericKind::Param(ref p) => write!(f, "{p:?}"),
GenericKind::Placeholder(ref p) => write!(f, "{p:?}"),
GenericKind::Alias(ref p) => write!(f, "{p:?}"),
}
}
@ -716,6 +718,7 @@ impl<'tcx> fmt::Display for GenericKind<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
GenericKind::Param(ref p) => write!(f, "{p}"),
GenericKind::Placeholder(ref p) => write!(f, "{p:?}"),
GenericKind::Alias(ref p) => write!(f, "{p}"),
}
}
@ -725,6 +728,7 @@ impl<'tcx> GenericKind<'tcx> {
pub fn to_ty(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
match *self {
GenericKind::Param(ref p) => p.to_ty(tcx),
GenericKind::Placeholder(ref p) => Ty::new_placeholder(tcx, *p),
GenericKind::Alias(ref p) => p.to_ty(tcx),
}
}

View File

@ -365,6 +365,11 @@ fn elaborate(&mut self, elaboratable: &O) {
Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, r_min)))
}
Component::Placeholder(p) => {
let ty = Ty::new_placeholder(tcx, p);
Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, r_min)))
}
Component::UnresolvedInferenceVariable(_) => None,
Component::Alias(alias_ty) => {

View File

@ -398,7 +398,8 @@ fn impl_intersection_has_negative_obligation(
debug!("negative_impl(impl1_def_id={:?}, impl2_def_id={:?})", impl1_def_id, impl2_def_id);
let ref infcx = tcx.infer_ctxt().intercrate(true).with_next_trait_solver(true).build();
let universe = infcx.universe();
let root_universe = infcx.universe();
assert_eq!(root_universe, ty::UniverseIndex::ROOT);
let impl1_header = fresh_impl_header(infcx, impl1_def_id);
let param_env =
@ -414,7 +415,12 @@ fn impl_intersection_has_negative_obligation(
return false;
};
plug_infer_with_placeholders(infcx, universe, (impl1_header.impl_args, impl2_header.impl_args));
plug_infer_with_placeholders(
infcx,
root_universe,
(impl1_header.impl_args, impl2_header.impl_args),
);
let param_env = infcx.resolve_vars_if_possible(param_env);
// FIXME(with_negative_coherence): the infcx has constraints from equating
// the impl headers. We should use these constraints as assumptions, not as

View File

@ -208,6 +208,9 @@ fn implied_bounds_from_components<'tcx>(
Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)),
Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)),
Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)),
Component::Placeholder(_) => {
unimplemented!("Shouldn't expect a placeholder type in implied bounds (yet)")
}
Component::EscapingAlias(_) =>
// If the projection has escaping regions, don't
// try to infer any implied bounds even for its

View File

@ -0,0 +1,14 @@
#![feature(negative_impls)]
#![feature(with_negative_coherence)]
struct Wrap<T>(T);
trait Foo {}
impl<T: 'static> !Foo for Box<T> {}
trait Bar {}
impl<T> Bar for T where T: Foo {}
impl<T> Bar for Box<T> {}
//~^ ERROR conflicting implementations of trait `Bar` for type `Box<_>`
fn main() {}

View File

@ -0,0 +1,11 @@
error[E0119]: conflicting implementations of trait `Bar` for type `Box<_>`
--> $DIR/negative-coherence-check-placeholder-outlives.rs:11:1
|
LL | impl<T> Bar for T where T: Foo {}
| ------------------------------ first implementation here
LL | impl<T> Bar for Box<T> {}
| ^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Box<_>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0119`.

View File

@ -0,0 +1,26 @@
warning: the feature `non_lifetime_binders` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/placeholders-dont-outlive-static.rs:6:12
|
LL | #![feature(non_lifetime_binders)]
| ^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #108185 <https://github.com/rust-lang/rust/issues/108185> for more information
= note: `#[warn(incomplete_features)]` on by default
error[E0310]: the placeholder type `!1_"T"` may not live long enough
--> $DIR/placeholders-dont-outlive-static.rs:13:5
|
LL | foo();
| ^^^^^
| |
| the placeholder type `!1_"T"` must be valid for the static lifetime...
| ...so that the type `T` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
LL | fn bad() where !1_"T": 'static {
| +++++++++++++++++++++
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0310`.

View File

@ -0,0 +1,26 @@
warning: the feature `non_lifetime_binders` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/placeholders-dont-outlive-static.rs:6:12
|
LL | #![feature(non_lifetime_binders)]
| ^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #108185 <https://github.com/rust-lang/rust/issues/108185> for more information
= note: `#[warn(incomplete_features)]` on by default
error[E0310]: the placeholder type `!1_"T"` may not live long enough
--> $DIR/placeholders-dont-outlive-static.rs:19:5
|
LL | foo();
| ^^^^^
| |
| the placeholder type `!1_"T"` must be valid for the static lifetime...
| ...so that the type `T` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
LL | fn good() where for<T> T: 'static, !1_"T": 'static {
| +++++++++++++++++
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0310`.

View File

@ -0,0 +1,22 @@
// revisions: good bad
//[good] known-bug: unknown
// `for<T> T: 'static` doesn't imply itself when processing outlives obligations
#![feature(non_lifetime_binders)]
//[bad]~^ WARN the feature `non_lifetime_binders` is incomplete
fn foo() where for<T> T: 'static {}
#[cfg(bad)]
fn bad() {
foo();
//[bad]~^ ERROR the placeholder type `!1_"T"` may not live long enough
}
#[cfg(good)]
fn good() where for<T> T: 'static {
foo();
}
fn main() {}

View File

@ -1,10 +1,9 @@
// edition:2021
// check-pass
// known-bug: unknown
// Checks that test_type_match code doesn't ICE when predicates have late-bound types
#![feature(non_lifetime_binders)]
//~^ WARN is incomplete and may not be safe to use
async fn walk2<'a, T: 'a>(_: T)
where

View File

@ -7,5 +7,20 @@ LL | #![feature(non_lifetime_binders)]
= note: see issue #108185 <https://github.com/rust-lang/rust/issues/108185> for more information
= note: `#[warn(incomplete_features)]` on by default
warning: 1 warning emitted
error[E0309]: the placeholder type `!1_"F"` may not live long enough
--> $DIR/type-match-with-late-bound.rs:11:1
|
LL | async fn walk2<'a, T: 'a>(_: T)
| -- the placeholder type `!1_"F"` must be valid for the lifetime `'a` as defined here...
...
LL | {}
| ^^ ...so that the type `F` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
LL | for<F> F: 'a, !1_"F": 'a
| ~~~~~~~~~~~~
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0309`.