Auto merge of #103491 - cjgillot:self-rpit, r=oli-obk

Support using `Self` or projections inside an RPIT/async fn

I reuse the same idea as https://github.com/rust-lang/rust/pull/103449 to use variances to encode whether a lifetime parameter is captured by impl-trait.

The current implementation of async and RPIT replace all lifetimes from the parent generics by `'static`.  This PR changes the scheme
```rust
impl<'a> Foo<'a> {
    fn foo<'b, T>() -> impl Into<Self> + 'b { ... }
}

opaque Foo::<'_a>::foo::<'_b, T>::opaque<'b>: Into<Foo<'_a>> + 'b;
impl<'a> Foo<'a> {
    // OLD
    fn foo<'b, T>() -> Foo::<'static>::foo::<'static, T>::opaque::<'b> { ... }
                             ^^^^^^^ the `Self` becomes `Foo<'static>`

    // NEW
    fn foo<'b, T>() -> Foo::<'a>::foo::<'b, T>::opaque::<'b> { ... }
                             ^^ the `Self` stays `Foo<'a>`
}
```

There is the same issue with projections. In the example, substitute `Self` by `<T as Trait<'b>>::Assoc` in the sugared version, and `Foo<'_a>` by `<T as Trait<'_b>>::Assoc` in the desugared one.

This allows to support `Self` in impl-trait, since we do not replace lifetimes by `'static` any more.  The same trick allows to use projections like `T::Assoc` where `Self` is allowed.  The feature is gated behind a `impl_trait_projections` feature gate.

The implementation relies on 2 tweaking rules for opaques in 2 places:
- we only relate substs that correspond to captured lifetimes during TypeRelation;
- we only list captured lifetimes in choice region computation.

For simplicity, I encoded the "capturedness" of lifetimes as a variance, `Bivariant` vs `Invariant` for unused vs captured lifetimes. The `variances_of` query used to ICE for opaques.

Impl-trait that do not reference `Self` or projections will have their variances as:
- `o` (invariant) for each parent type or const;
- `*` (bivariant) for each parent lifetime --> will not participate in borrowck;
- `o` (invariant) for each own lifetime.

Impl-trait that does reference `Self` and/or projections will have some parent lifetimes marked as `o` (as the example above), and participate in type relation and borrowck.  In the example above, `variances_of(opaque) = ['_a: o, '_b: *, T: o, 'b: o]`.

r? types
cc `@compiler-errors` , as you asked about the issue with `Self` and projections.
This commit is contained in:
bors 2022-11-21 12:17:03 +00:00
commit 7fe6f36224
33 changed files with 570 additions and 341 deletions

View file

@ -2777,35 +2777,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
let substs = InternalSubsts::for_item(tcx, def_id, |param, _| {
if let Some(i) = (param.index as usize).checked_sub(generics.parent_count) {
// Our own parameters are the resolved lifetimes.
if let GenericParamDefKind::Lifetime = param.kind {
if let hir::GenericArg::Lifetime(lifetime) = &lifetimes[i] {
self.ast_region_to_region(lifetime, None).into()
} else {
bug!()
}
} else {
bug!()
}
let GenericParamDefKind::Lifetime { .. } = param.kind else { bug!() };
let hir::GenericArg::Lifetime(lifetime) = &lifetimes[i] else { bug!() };
self.ast_region_to_region(lifetime, None).into()
} else {
match param.kind {
// For RPIT (return position impl trait), only lifetimes
// mentioned in the impl Trait predicate are captured by
// the opaque type, so the lifetime parameters from the
// parent item need to be replaced with `'static`.
//
// For `impl Trait` in the types of statics, constants,
// locals and type aliases. These capture all parent
// lifetimes, so they can use their identity subst.
GenericParamDefKind::Lifetime
if matches!(
origin,
hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..)
) =>
{
tcx.lifetimes.re_static.into()
}
_ => tcx.mk_param_from_def(param),
}
tcx.mk_param_from_def(param)
}
});
debug!("impl_trait_ty_to_ty: substs={:?}", substs);
@ -2982,6 +2958,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
Some(tcx.liberate_late_bound_regions(fn_hir_id.expect_owner().to_def_id(), ty))
}
#[instrument(level = "trace", skip(self, generate_err))]
fn validate_late_bound_regions(
&self,
constrained_regions: FxHashSet<ty::BoundRegionKind>,

View file

@ -10,6 +10,7 @@ use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{ItemKind, Node, PathSegment};
use rustc_infer::infer::opaque_types::ConstrainOpaqueTypeRegionVisitor;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::{DefiningAnchor, RegionVariableOrigin, TyCtxtInferExt};
use rustc_infer::traits::Obligation;
@ -229,7 +230,9 @@ fn check_opaque<'tcx>(tcx: TyCtxt<'tcx>, id: hir::ItemId) {
let substs = InternalSubsts::identity_for_item(tcx, item.owner_id.to_def_id());
let span = tcx.def_span(item.owner_id.def_id);
check_opaque_for_inheriting_lifetimes(tcx, item.owner_id.def_id, span);
if !tcx.features().impl_trait_projections {
check_opaque_for_inheriting_lifetimes(tcx, item.owner_id.def_id, span);
}
if tcx.type_of(item.owner_id.def_id).references_error() {
return;
}
@ -238,6 +241,7 @@ fn check_opaque<'tcx>(tcx: TyCtxt<'tcx>, id: hir::ItemId) {
}
check_opaque_meets_bounds(tcx, item.owner_id.def_id, substs, span, &origin);
}
/// Checks that an opaque type does not use `Self` or `T::Foo` projections that would result
/// in "inheriting lifetimes".
#[instrument(level = "debug", skip(tcx, span))]
@ -249,39 +253,11 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
let item = tcx.hir().expect_item(def_id);
debug!(?item, ?span);
struct FoundParentLifetime;
struct FindParentLifetimeVisitor<'tcx>(&'tcx ty::Generics);
impl<'tcx> ty::visit::TypeVisitor<'tcx> for FindParentLifetimeVisitor<'tcx> {
type BreakTy = FoundParentLifetime;
fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
debug!("FindParentLifetimeVisitor: r={:?}", r);
if let ty::ReEarlyBound(ty::EarlyBoundRegion { index, .. }) = *r {
if index < self.0.parent_count as u32 {
return ControlFlow::Break(FoundParentLifetime);
} else {
return ControlFlow::CONTINUE;
}
}
r.super_visit_with(self)
}
fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::ConstKind::Unevaluated(..) = c.kind() {
// FIXME(#72219) We currently don't detect lifetimes within substs
// which would violate this check. Even though the particular substitution is not used
// within the const, this should still be fixed.
return ControlFlow::CONTINUE;
}
c.super_visit_with(self)
}
}
struct ProhibitOpaqueVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
opaque_identity_ty: Ty<'tcx>,
generics: &'tcx ty::Generics,
parent_count: u32,
references_parent_regions: bool,
selftys: Vec<(Span, Option<String>)>,
}
@ -289,12 +265,25 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
type BreakTy = Ty<'tcx>;
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
debug!("check_opaque_for_inheriting_lifetimes: (visit_ty) t={:?}", t);
debug!(?t, "root_visit_ty");
if t == self.opaque_identity_ty {
ControlFlow::CONTINUE
} else {
t.super_visit_with(&mut FindParentLifetimeVisitor(self.generics))
.map_break(|FoundParentLifetime| t)
t.visit_with(&mut ConstrainOpaqueTypeRegionVisitor {
tcx: self.tcx,
op: |region| {
if let ty::ReEarlyBound(ty::EarlyBoundRegion { index, .. }) = *region
&& index < self.parent_count
{
self.references_parent_regions= true;
}
},
});
if self.references_parent_regions {
ControlFlow::Break(t)
} else {
ControlFlow::CONTINUE
}
}
}
}
@ -327,15 +316,20 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
if let ItemKind::OpaqueTy(hir::OpaqueTy {
origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..),
in_trait,
..
}) = item.kind
{
let substs = InternalSubsts::identity_for_item(tcx, def_id.to_def_id());
let opaque_identity_ty = if in_trait {
tcx.mk_projection(def_id.to_def_id(), substs)
} else {
tcx.mk_opaque(def_id.to_def_id(), substs)
};
let mut visitor = ProhibitOpaqueVisitor {
opaque_identity_ty: tcx.mk_opaque(
def_id.to_def_id(),
InternalSubsts::identity_for_item(tcx, def_id.to_def_id()),
),
generics: tcx.generics_of(def_id),
opaque_identity_ty,
parent_count: tcx.generics_of(def_id).parent_count as u32,
references_parent_regions: false,
tcx,
selftys: vec![],
};
@ -343,10 +337,6 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
.explicit_item_bounds(def_id)
.iter()
.try_for_each(|(predicate, _)| predicate.visit_with(&mut visitor));
debug!(
"check_opaque_for_inheriting_lifetimes: prohibit_opaque={:?}, visitor.opaque_identity_ty={:?}, visitor.generics={:?}",
prohibit_opaque, visitor.opaque_identity_ty, visitor.generics
);
if let Some(ty) = prohibit_opaque.break_value() {
visitor.visit_item(&item);
@ -357,15 +347,16 @@ pub(super) fn check_opaque_for_inheriting_lifetimes<'tcx>(
_ => unreachable!(),
};
let mut err = struct_span_err!(
tcx.sess,
let mut err = feature_err(
&tcx.sess.parse_sess,
sym::impl_trait_projections,
span,
E0760,
"`{}` return type cannot contain a projection or `Self` that references lifetimes from \
a parent scope",
if is_async { "async fn" } else { "impl Trait" },
&format!(
"`{}` return type cannot contain a projection or `Self` that references \
lifetimes from a parent scope",
if is_async { "async fn" } else { "impl Trait" },
),
);
for (span, name) in visitor.selftys {
err.span_suggestion(
span,

View file

@ -1539,7 +1539,6 @@ fn check_fn_or_method<'tcx>(
check_return_position_impl_trait_in_trait_bounds(
tcx,
wfcx,
def_id,
sig.output(),
hir_decl.output.span(),
@ -1575,9 +1574,9 @@ fn check_fn_or_method<'tcx>(
/// Basically `check_associated_type_bounds`, but separated for now and should be
/// deduplicated when RPITITs get lowered into real associated items.
#[tracing::instrument(level = "trace", skip(tcx))]
fn check_return_position_impl_trait_in_trait_bounds<'tcx>(
tcx: TyCtxt<'tcx>,
wfcx: &WfCheckingCtxt<'_, 'tcx>,
fn_def_id: LocalDefId,
fn_output: Ty<'tcx>,
span: Span,
@ -1591,18 +1590,22 @@ fn check_return_position_impl_trait_in_trait_bounds<'tcx>(
&& tcx.def_kind(proj.item_def_id) == DefKind::ImplTraitPlaceholder
&& tcx.impl_trait_in_trait_parent(proj.item_def_id) == fn_def_id.to_def_id()
{
let bounds = wfcx.tcx().explicit_item_bounds(proj.item_def_id);
let wf_obligations = bounds.iter().flat_map(|&(bound, bound_span)| {
let normalized_bound = wfcx.normalize(span, None, bound);
traits::wf::predicate_obligations(
wfcx.infcx,
wfcx.param_env,
wfcx.body_id,
normalized_bound,
bound_span,
)
// Create a new context, since we want the opaque's ParamEnv and not the parent's.
let span = tcx.def_span(proj.item_def_id);
enter_wf_checking_ctxt(tcx, span, proj.item_def_id.expect_local(), |wfcx| {
let bounds = wfcx.tcx().explicit_item_bounds(proj.item_def_id);
let wf_obligations = bounds.iter().flat_map(|&(bound, bound_span)| {
let normalized_bound = wfcx.normalize(span, None, bound);
traits::wf::predicate_obligations(
wfcx.infcx,
wfcx.param_env,
wfcx.body_id,
normalized_bound,
bound_span,
)
});
wfcx.register_obligations(wf_obligations);
});
wfcx.register_obligations(wf_obligations);
}
}
}

View file

@ -84,60 +84,30 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericP
Node::ImplItem(item) => item.generics,
Node::Item(item) => {
match item.kind {
ItemKind::Impl(ref impl_) => {
if impl_.defaultness.is_default() {
is_default_impl_trait = tcx.impl_trait_ref(def_id).map(ty::Binder::dummy);
}
&impl_.generics
Node::Item(item) => match item.kind {
ItemKind::Impl(ref impl_) => {
if impl_.defaultness.is_default() {
is_default_impl_trait = tcx.impl_trait_ref(def_id).map(ty::Binder::dummy);
}
ItemKind::Fn(.., ref generics, _)
| ItemKind::TyAlias(_, ref generics)
| ItemKind::Enum(_, ref generics)
| ItemKind::Struct(_, ref generics)
| ItemKind::Union(_, ref generics) => *generics,
ItemKind::Trait(_, _, ref generics, ..) => {
is_trait = Some(ty::TraitRef::identity(tcx, def_id));
*generics
}
ItemKind::TraitAlias(ref generics, _) => {
is_trait = Some(ty::TraitRef::identity(tcx, def_id));
*generics
}
ItemKind::OpaqueTy(OpaqueTy {
origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..),
..
}) => {
// return-position impl trait
//
// We don't inherit predicates from the parent here:
// If we have, say `fn f<'a, T: 'a>() -> impl Sized {}`
// then the return type is `f::<'static, T>::{{opaque}}`.
//
// If we inherited the predicates of `f` then we would
// require that `T: 'static` to show that the return
// type is well-formed.
//
// The only way to have something with this opaque type
// is from the return type of the containing function,
// which will ensure that the function's predicates
// hold.
return ty::GenericPredicates { parent: None, predicates: &[] };
}
ItemKind::OpaqueTy(OpaqueTy {
ref generics,
origin: hir::OpaqueTyOrigin::TyAlias,
..
}) => {
// type-alias impl trait
generics
}
_ => NO_GENERICS,
&impl_.generics
}
}
ItemKind::Fn(.., ref generics, _)
| ItemKind::TyAlias(_, ref generics)
| ItemKind::Enum(_, ref generics)
| ItemKind::Struct(_, ref generics)
| ItemKind::Union(_, ref generics) => *generics,
ItemKind::Trait(_, _, ref generics, ..) => {
is_trait = Some(ty::TraitRef::identity(tcx, def_id));
*generics
}
ItemKind::TraitAlias(ref generics, _) => {
is_trait = Some(ty::TraitRef::identity(tcx, def_id));
*generics
}
ItemKind::OpaqueTy(OpaqueTy { ref generics, .. }) => generics,
_ => NO_GENERICS,
},
Node::ForeignItem(item) => match item.kind {
ForeignItemKind::Static(..) => NO_GENERICS,
@ -181,6 +151,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericP
trace!(?predicates);
trace!(?ast_generics);
trace!(?generics);
// Collect the predicates that were written inline by the user on each
// type parameter (e.g., `<T: Foo>`).
@ -299,6 +270,54 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericP
);
}
// Opaque types duplicate some of their generic parameters.
// We create bi-directional Outlives predicates between the original
// and the duplicated parameter, to ensure that they do not get out of sync.
if let Node::Item(&Item { kind: ItemKind::OpaqueTy(..), .. }) = node {
let opaque_ty_id = tcx.hir().get_parent_node(hir_id);
let opaque_ty_node = tcx.hir().get(opaque_ty_id);
let Node::Ty(&Ty { kind: TyKind::OpaqueDef(_, lifetimes, _), .. }) = opaque_ty_node else {
bug!("unexpected {opaque_ty_node:?}")
};
debug!(?lifetimes);
for (arg, duplicate) in std::iter::zip(lifetimes, ast_generics.params) {
let hir::GenericArg::Lifetime(arg) = arg else { bug!() };
let orig_region = <dyn AstConv<'_>>::ast_region_to_region(&icx, &arg, None);
if !matches!(orig_region.kind(), ty::ReEarlyBound(..)) {
// Only early-bound regions can point to the original generic parameter.
continue;
}
let hir::GenericParamKind::Lifetime { .. } = duplicate.kind else { continue };
let dup_def = tcx.hir().local_def_id(duplicate.hir_id).to_def_id();
let Some(dup_index) = generics.param_def_id_to_index(tcx, dup_def) else { bug!() };
let dup_region = tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion {
def_id: dup_def,
index: dup_index,
name: duplicate.name.ident().name,
}));
predicates.push((
ty::Binder::dummy(ty::PredicateKind::RegionOutlives(ty::OutlivesPredicate(
orig_region,
dup_region,
)))
.to_predicate(icx.tcx),
duplicate.span,
));
predicates.push((
ty::Binder::dummy(ty::PredicateKind::RegionOutlives(ty::OutlivesPredicate(
dup_region,
orig_region,
)))
.to_predicate(icx.tcx),
duplicate.span,
));
}
debug!(?predicates);
}
ty::GenericPredicates {
parent: generics.parent,
predicates: tcx.arena.alloc_from_iter(predicates),

View file

@ -5,9 +5,10 @@
use rustc_arena::DroplessArena;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{self, CrateVariancesMap, TyCtxt};
use rustc_middle::ty::{self, CrateVariancesMap, TyCtxt, TypeSuperVisitable, TypeVisitable};
use std::ops::ControlFlow;
/// Defines the `TermsContext` basically houses an arena where we can
/// allocate terms.
@ -50,6 +51,9 @@ fn variances_of(tcx: TyCtxt<'_>, item_def_id: DefId) -> &[ty::Variance] {
| DefKind::Union
| DefKind::Variant
| DefKind::Ctor(..) => {}
DefKind::OpaqueTy | DefKind::ImplTraitPlaceholder => {
return variance_of_opaque(tcx, item_def_id.expect_local());
}
_ => {
// Variance not relevant.
span_bug!(tcx.def_span(item_def_id), "asked to compute variance for wrong kind of item")
@ -61,3 +65,89 @@ fn variances_of(tcx: TyCtxt<'_>, item_def_id: DefId) -> &[ty::Variance] {
let crate_map = tcx.crate_variances(());
crate_map.variances.get(&item_def_id).copied().unwrap_or(&[])
}
#[instrument(level = "trace", skip(tcx), ret)]
fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Variance] {
let generics = tcx.generics_of(item_def_id);
// Opaque types may only use regions that are bound. So for
// ```rust
// type Foo<'a, 'b, 'c> = impl Trait<'a> + 'b;
// ```
// we may not use `'c` in the hidden type.
struct OpaqueTypeLifetimeCollector {
variances: Vec<ty::Variance>,
}
impl<'tcx> ty::TypeVisitor<'tcx> for OpaqueTypeLifetimeCollector {
#[instrument(level = "trace", skip(self), ret)]
fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::RegionKind::ReEarlyBound(ebr) = r.kind() {
self.variances[ebr.index as usize] = ty::Invariant;
}
r.super_visit_with(self)
}
}
// By default, RPIT are invariant wrt type and const generics, but they are bivariant wrt
// lifetime generics.
let mut variances: Vec<_> = std::iter::repeat(ty::Invariant).take(generics.count()).collect();
// Mark all lifetimes from parent generics as unused (Bivariant).
// This will be overridden later if required.
{
let mut generics = generics;
while let Some(def_id) = generics.parent {
generics = tcx.generics_of(def_id);
for param in &generics.params {
match param.kind {
ty::GenericParamDefKind::Lifetime => {
variances[param.index as usize] = ty::Bivariant;
}
ty::GenericParamDefKind::Type { .. }
| ty::GenericParamDefKind::Const { .. } => {}
}
}
}
}
let mut collector = OpaqueTypeLifetimeCollector { variances };
let id_substs = ty::InternalSubsts::identity_for_item(tcx, item_def_id.to_def_id());
for pred in tcx.bound_explicit_item_bounds(item_def_id.to_def_id()).transpose_iter() {
let pred = pred.map_bound(|(pred, _)| *pred).subst(tcx, id_substs);
debug!(?pred);
// We only ignore opaque type substs if the opaque type is the outermost type.
// The opaque type may be nested within itself via recursion in e.g.
// type Foo<'a> = impl PartialEq<Foo<'a>>;
// which thus mentions `'a` and should thus accept hidden types that borrow 'a
// instead of requiring an additional `+ 'a`.
match pred.kind().skip_binder() {
ty::PredicateKind::Trait(ty::TraitPredicate {
trait_ref: ty::TraitRef { def_id: _, substs },
constness: _,
polarity: _,
}) => {
for subst in &substs[1..] {
subst.visit_with(&mut collector);
}
}
ty::PredicateKind::Projection(ty::ProjectionPredicate {
projection_ty: ty::ProjectionTy { substs, item_def_id: _ },
term,
}) => {
for subst in &substs[1..] {
subst.visit_with(&mut collector);
}
term.visit_with(&mut collector);
}
ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(_, region)) => {
region.visit_with(&mut collector);
}
_ => {
pred.visit_with(&mut collector);
}
}
}
tcx.arena.alloc_from_iter(collector.variances.into_iter())
}