Skip to content

Commit

Permalink
Rollup merge of #118000 - compiler-errors:placeholder-ty-outlives, r=…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
compiler-errors committed Nov 20, 2023
2 parents 40a781b + 8f267e2 commit 6388c0e
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 13 deletions.
4 changes: 4 additions & 0 deletions compiler/rustc_hir_analysis/src/outlives/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_infer/src/infer/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2351,6 +2351,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {

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}`")
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_infer/src/infer/outlives/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use smallvec::{smallvec, SmallVec};
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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
23 changes: 22 additions & 1 deletion compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ where
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);
Expand All @@ -267,10 +270,28 @@ where
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,
Expand Down
15 changes: 9 additions & 6 deletions compiler/rustc_infer/src/infer/outlives/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
}

#[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 {
Expand All @@ -51,7 +51,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, '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![]);
}
}
Expand Down Expand Up @@ -168,7 +168,10 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
) -> 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)
Expand All @@ -195,9 +198,9 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
/// 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)
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_infer/src/infer/region_constraints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),
}

Expand Down Expand Up @@ -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:?}"),
}
}
Expand All @@ -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}"),
}
}
Expand All @@ -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),
}
}
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_infer/src/traits/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, 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) => {
Expand Down
10 changes: 8 additions & 2 deletions compiler/rustc_trait_selection/src/traits/coherence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

0 comments on commit 6388c0e

Please sign in to comment.