Auto merge of #112682 - spastorino:new-rpitit-21, r=compiler-errors

Add bidirectional where clauses on RPITIT synthesized GATs

Given the following:

```rust
struct MyStruct<'a, T>(&'a T);

trait MyTrait<'a, T> {
    fn my_fn<'b, 'c, 'd, V>(item: &'c String) -> impl Sized + 'a + 'b + 'c where V: 'b, V: 'd;
}

impl<'a, T> MyTrait<'a, T> for MyStruct<'a, T> {
    fn my_fn<'b, 'c, 'd, V>(_: &'c String) -> impl Sized + 'a + 'b + 'c
    where
        V: 'b,
        V: 'd,
    {
        unimplemented!();
    }
}
```

We need the desugaring to be:

```rust
trait MyTrait<'a, T> {
    type MyFn<'bf, 'df, Vf, 'a2, 'b2, 'c2>: Sized + 'a2 + 'b2 + 'c2 where Vf: 'b2, 'a2: 'a, 'a: 'a2, 'b2: 'bf, 'bf: 'b2;

    fn my_fn<'b, 'c, 'd, V>(item: &'c String) -> MyStruct<'a>::MyFn<'b, 'd, V, 'a, 'b, 'c> where V: 'b, V: 'd {
        type opaque<'a3, 'b3, 'c3>;
    };
}

impl<'a, T> MyIter<'a, T> for MyStruct<'a, T> {
    type MyFn<'bf, 'df, Vf, 'a2, 'b2, 'c2> = impl Sized + 'a2 + 'b2 + 'c2 where Vf: b2, 'a2: 'a, 'a: 'a2, 'b2: 'bf, 'bf: 'b2;

    fn my_fn<'b, 'c, 'd, V>(_: &'c String) -> MyStruct<'a>::MyFn<'a, 'b, 'c, V> where V: 'b, V: 'd {
        type opaque<'a3, 'b3, 'c3>;
        unimplemented!();
    }
}
```

This PR adds the where clauses for the `MyFn` generated GATs.

This is a draft with a very ugly solution so we can make comments over concrete code.

r? `@compiler-errors`
This commit is contained in:
bors 2023-06-29 21:24:51 +00:00
commit 330727467b
13 changed files with 208 additions and 105 deletions

View File

@ -1539,9 +1539,6 @@ fn lower_opaque_impl_trait(
);
debug!(?opaque_ty_def_id);
// Contains the new lifetime definitions created for the TAIT (if any).
let mut collected_lifetimes = Vec::new();
// If this came from a TAIT (as opposed to a function that returns an RPIT), we only want
// to capture the lifetimes that appear in the bounds. So visit the bounds to find out
// exactly which ones those are.
@ -1558,20 +1555,31 @@ fn lower_opaque_impl_trait(
};
debug!(?lifetimes_to_remap);
let mut new_remapping = FxHashMap::default();
// Contains the new lifetime definitions created for the TAIT (if any).
// If this opaque type is only capturing a subset of the lifetimes (those that appear in
// bounds), then create the new lifetime parameters required and create a mapping from the
// old `'a` (on the function) to the new `'a` (on the opaque type).
let collected_lifetimes =
self.create_lifetime_defs(opaque_ty_def_id, &lifetimes_to_remap, &mut new_remapping);
debug!(?collected_lifetimes);
debug!(?new_remapping);
// This creates HIR lifetime arguments as `hir::GenericArg`, in the given example `type
// TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection containing `&['x]`.
let collected_lifetime_mapping: Vec<_> = collected_lifetimes
.iter()
.map(|(node_id, lifetime)| {
let id = self.next_node_id();
let lifetime = self.new_named_lifetime(lifetime.id, id, lifetime.ident);
let def_id = self.local_def_id(*node_id);
(lifetime, def_id)
})
.collect();
debug!(?collected_lifetime_mapping);
self.with_hir_id_owner(opaque_ty_node_id, |lctx| {
let mut new_remapping = FxHashMap::default();
// If this opaque type is only capturing a subset of the lifetimes (those that appear
// in bounds), then create the new lifetime parameters required and create a mapping
// from the old `'a` (on the function) to the new `'a` (on the opaque type).
collected_lifetimes = lctx.create_lifetime_defs(
opaque_ty_def_id,
&lifetimes_to_remap,
&mut new_remapping,
);
debug!(?collected_lifetimes);
debug!(?new_remapping);
// Install the remapping from old to new (if any):
lctx.with_remapping(new_remapping, |lctx| {
// This creates HIR lifetime definitions as `hir::GenericParam`, in the given
@ -1610,6 +1618,16 @@ fn lower_opaque_impl_trait(
let hir_bounds = lctx.lower_param_bounds(bounds, itctx);
debug!(?hir_bounds);
let lifetime_mapping = if in_trait {
self.arena.alloc_from_iter(
collected_lifetime_mapping
.iter()
.map(|(lifetime, def_id)| (**lifetime, *def_id)),
)
} else {
&mut []
};
let opaque_ty_item = hir::OpaqueTy {
generics: self.arena.alloc(hir::Generics {
params: lifetime_defs,
@ -1620,6 +1638,7 @@ fn lower_opaque_impl_trait(
}),
bounds: hir_bounds,
origin,
lifetime_mapping,
in_trait,
};
debug!(?opaque_ty_item);
@ -1628,20 +1647,14 @@ fn lower_opaque_impl_trait(
})
});
// This creates HIR lifetime arguments as `hir::GenericArg`, in the given example `type
// TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection containing `&['x]`.
let lifetimes =
self.arena.alloc_from_iter(collected_lifetimes.into_iter().map(|(_, lifetime)| {
let id = self.next_node_id();
let l = self.new_named_lifetime(lifetime.id, id, lifetime.ident);
hir::GenericArg::Lifetime(l)
}));
debug!(?lifetimes);
// `impl Trait` now just becomes `Foo<'a, 'b, ..>`.
hir::TyKind::OpaqueDef(
hir::ItemId { owner_id: hir::OwnerId { def_id: opaque_ty_def_id } },
lifetimes,
self.arena.alloc_from_iter(
collected_lifetime_mapping
.iter()
.map(|(lifetime, _)| hir::GenericArg::Lifetime(*lifetime)),
),
in_trait,
)
}
@ -1655,7 +1668,7 @@ fn generate_opaque_type(
span: Span,
opaque_ty_span: Span,
) -> hir::OwnerNode<'hir> {
let opaque_ty_item_kind = hir::ItemKind::OpaqueTy(opaque_ty_item);
let opaque_ty_item_kind = hir::ItemKind::OpaqueTy(self.arena.alloc(opaque_ty_item));
// Generate an `type Foo = impl Trait;` declaration.
trace!("registering opaque type with id {:#?}", opaque_ty_id);
let opaque_ty_item = hir::Item {
@ -1983,7 +1996,6 @@ fn lower_async_fn_ret_ty(
let lifetime = Lifetime { id: outer_node_id, ident };
collected_lifetimes.push((inner_node_id, lifetime, Some(inner_res)));
}
debug!(?collected_lifetimes);
// We only want to capture the lifetimes that appear in the bounds. So visit the bounds to
@ -1993,22 +2005,36 @@ fn lower_async_fn_ret_ty(
let lifetimes_to_remap = lifetime_collector::lifetimes_in_ret_ty(&self.resolver, output);
debug!(?lifetimes_to_remap);
self.with_hir_id_owner(opaque_ty_node_id, |this| {
// If this opaque type is only capturing a subset of the lifetimes (those that appear
// in bounds), then create the new lifetime parameters required and create a mapping
// from the old `'a` (on the function) to the new `'a` (on the opaque type).
collected_lifetimes.extend(
this.create_lifetime_defs(
opaque_ty_def_id,
&lifetimes_to_remap,
&mut new_remapping,
)
// If this opaque type is only capturing a subset of the lifetimes (those that appear in
// bounds), then create the new lifetime parameters required and create a mapping from the
// old `'a` (on the function) to the new `'a` (on the opaque type).
collected_lifetimes.extend(
self.create_lifetime_defs(opaque_ty_def_id, &lifetimes_to_remap, &mut new_remapping)
.into_iter()
.map(|(new_node_id, lifetime)| (new_node_id, lifetime, None)),
);
debug!(?collected_lifetimes);
debug!(?new_remapping);
);
debug!(?collected_lifetimes);
debug!(?new_remapping);
// This creates pairs of HIR lifetimes and def_ids. In the given example `type
// TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection containing the
// new lifetime of the RPIT 'x and the def_id of the lifetime 'x corresponding to
// `TestReturn`.
let collected_lifetime_mapping: Vec<_> = collected_lifetimes
.iter()
.map(|(node_id, lifetime, res)| {
let id = self.next_node_id();
let res = res.unwrap_or(
self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error),
);
let lifetime = self.new_named_lifetime_with_res(id, lifetime.ident, res);
let def_id = self.local_def_id(*node_id);
(lifetime, def_id)
})
.collect();
debug!(?collected_lifetime_mapping);
self.with_hir_id_owner(opaque_ty_node_id, |this| {
// Install the remapping from old to new (if any):
this.with_remapping(new_remapping, |this| {
// We have to be careful to get elision right here. The
@ -2063,6 +2089,16 @@ fn lower_async_fn_ret_ty(
));
debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params);
let lifetime_mapping = if in_trait {
self.arena.alloc_from_iter(
collected_lifetime_mapping
.iter()
.map(|(lifetime, def_id)| (**lifetime, *def_id)),
)
} else {
&mut []
};
let opaque_ty_item = hir::OpaqueTy {
generics: this.arena.alloc(hir::Generics {
params: generic_params,
@ -2073,6 +2109,7 @@ fn lower_async_fn_ret_ty(
}),
bounds: arena_vec![this; future_bound],
origin: hir::OpaqueTyOrigin::AsyncFn(fn_def_id),
lifetime_mapping,
in_trait,
};
@ -2096,15 +2133,11 @@ fn lower_async_fn_ret_ty(
//
// For the "output" lifetime parameters, we just want to
// generate `'_`.
let generic_args = self.arena.alloc_from_iter(collected_lifetimes.into_iter().map(
|(_, lifetime, res)| {
let id = self.next_node_id();
let res = res.unwrap_or(
self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error),
);
hir::GenericArg::Lifetime(self.new_named_lifetime_with_res(id, lifetime.ident, res))
},
));
let generic_args = self.arena.alloc_from_iter(
collected_lifetime_mapping
.iter()
.map(|(lifetime, _)| hir::GenericArg::Lifetime(*lifetime)),
);
// Create the `Foo<...>` reference itself. Note that the `type
// Foo = impl Trait` is, internally, created as a child of the

View File

@ -2664,6 +2664,10 @@ pub struct OpaqueTy<'hir> {
pub generics: &'hir Generics<'hir>,
pub bounds: GenericBounds<'hir>,
pub origin: OpaqueTyOrigin,
// Opaques have duplicated lifetimes, this mapping connects the original lifetime with the copy
// so we can later generate bidirectional outlives predicates to enforce that these lifetimes
// stay in sync.
pub lifetime_mapping: &'hir [(Lifetime, LocalDefId)],
pub in_trait: bool,
}
@ -3315,7 +3319,7 @@ pub enum ItemKind<'hir> {
/// A type alias, e.g., `type Foo = Bar<u8>`.
TyAlias(&'hir Ty<'hir>, &'hir Generics<'hir>),
/// An opaque `impl Trait` type alias, e.g., `type Foo = impl Bar;`.
OpaqueTy(OpaqueTy<'hir>),
OpaqueTy(&'hir OpaqueTy<'hir>),
/// An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`.
Enum(EnumDef<'hir>, &'hir Generics<'hir>),
/// A struct definition, e.g., `struct Foo<A> {x: A}`.

View File

@ -502,7 +502,7 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) {
visitor.visit_ty(ty);
visitor.visit_generics(generics)
}
ItemKind::OpaqueTy(OpaqueTy { ref generics, bounds, .. }) => {
ItemKind::OpaqueTy(&OpaqueTy { generics, bounds, .. }) => {
visitor.visit_id(item.hir_id());
walk_generics(visitor, generics);
walk_list!(visitor, visit_param_bound, bounds);

View File

@ -2809,7 +2809,7 @@ fn ast_ty_to_ty_inner(&self, ast_ty: &hir::Ty<'_>, borrowed: bool, in_path: bool
let opaque_ty = tcx.hir().item(item_id);
match opaque_ty.kind {
hir::ItemKind::OpaqueTy(hir::OpaqueTy { origin, .. }) => {
hir::ItemKind::OpaqueTy(&hir::OpaqueTy { origin, .. }) => {
let local_def_id = item_id.owner_id.def_id;
// If this is an RPITIT and we are using the new RPITIT lowering scheme, we
// generate the def_id of an associated type for the trait and return as

View File

@ -299,7 +299,7 @@ fn visit_ty(&mut self, arg: &'tcx hir::Ty<'tcx>) {
}
}
if let ItemKind::OpaqueTy(hir::OpaqueTy {
if let ItemKind::OpaqueTy(&hir::OpaqueTy {
origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..),
in_trait,
..

View File

@ -144,7 +144,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
Some(tcx.typeck_root_def_id(def_id.to_def_id()))
}
Node::Item(item) => match item.kind {
ItemKind::OpaqueTy(hir::OpaqueTy {
ItemKind::OpaqueTy(&hir::OpaqueTy {
origin:
hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id),
in_trait,

View File

@ -2,7 +2,7 @@
use crate::bounds::Bounds;
use crate::collect::ItemCtxt;
use crate::constrained_generic_params as cgp;
use hir::{HirId, Node};
use hir::{HirId, Lifetime, Node};
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
@ -10,9 +10,9 @@
use rustc_hir::intravisit::{self, Visitor};
use rustc_middle::ty::subst::InternalSubsts;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::ty::{GenericPredicates, ToPredicate};
use rustc_middle::ty::{GenericPredicates, Generics, ImplTraitInTraitData, ToPredicate};
use rustc_span::symbol::{sym, Ident};
use rustc_span::{Span, DUMMY_SP};
use rustc_span::{Span, Symbol, DUMMY_SP};
/// Returns a list of all type predicates (explicit and implicit) for the definition with
/// ID `def_id`. This includes all predicates returned by `predicates_defined_on`, plus
@ -62,6 +62,54 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic
fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::GenericPredicates<'_> {
use rustc_hir::*;
match tcx.opt_rpitit_info(def_id.to_def_id()) {
Some(ImplTraitInTraitData::Trait { opaque_def_id, .. }) => {
let opaque_ty_id = tcx.hir().local_def_id_to_hir_id(opaque_def_id.expect_local());
let opaque_ty_node = tcx.hir().get(opaque_ty_id);
let Node::Item(&Item { kind: ItemKind::OpaqueTy(OpaqueTy { lifetime_mapping, .. }), .. }) = opaque_ty_node else {
bug!("unexpected {opaque_ty_node:?}")
};
let mut predicates = Vec::new();
compute_bidirectional_outlives_predicates(
tcx,
def_id,
lifetime_mapping.iter().map(|(lifetime, def_id)| {
(*lifetime, (*def_id, lifetime.ident.name, lifetime.ident.span))
}),
tcx.generics_of(def_id.to_def_id()),
&mut predicates,
);
return ty::GenericPredicates {
parent: Some(tcx.parent(def_id.to_def_id())),
predicates: tcx.arena.alloc_from_iter(predicates),
};
}
Some(ImplTraitInTraitData::Impl { fn_def_id }) => {
let assoc_item = tcx.associated_item(def_id);
let trait_assoc_predicates = tcx.predicates_of(assoc_item.trait_item_def_id.unwrap());
let impl_assoc_identity_substs = InternalSubsts::identity_for_item(tcx, def_id);
let impl_def_id = tcx.parent(fn_def_id);
let impl_trait_ref_substs =
tcx.impl_trait_ref(impl_def_id).unwrap().skip_binder().substs;
let impl_assoc_substs =
impl_assoc_identity_substs.rebase_onto(tcx, impl_def_id, impl_trait_ref_substs);
let impl_predicates = trait_assoc_predicates.instantiate_own(tcx, impl_assoc_substs);
return ty::GenericPredicates {
parent: Some(impl_def_id),
predicates: tcx.arena.alloc_from_iter(impl_predicates),
};
}
None => {}
}
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
let node = tcx.hir().get(hir_id);
@ -289,38 +337,22 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
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 = icx.astconv().ast_region_to_region(&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 = duplicate.def_id.to_def_id();
let lifetime_mapping = std::iter::zip(lifetimes, ast_generics.params)
.map(|(arg, dup)| {
let hir::GenericArg::Lifetime(arg) = arg else { bug!() };
(**arg, dup)
})
.filter(|(_, dup)| matches!(dup.kind, hir::GenericParamKind::Lifetime { .. }))
.map(|(lifetime, dup)| (lifetime, (dup.def_id, dup.name.ident().name, dup.span)));
let Some(dup_index) = generics.param_def_id_to_index(tcx, dup_def) else { bug!() };
let dup_region = ty::Region::new_early_bound(
tcx,
ty::EarlyBoundRegion {
def_id: dup_def,
index: dup_index,
name: duplicate.name.ident().name,
},
);
predicates.push((
ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(orig_region, dup_region))
.to_predicate(icx.tcx),
duplicate.span,
));
predicates.push((
ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(dup_region, orig_region))
.to_predicate(icx.tcx),
duplicate.span,
));
}
compute_bidirectional_outlives_predicates(
tcx,
def_id,
lifetime_mapping,
generics,
&mut predicates,
);
debug!(?predicates);
}
@ -330,6 +362,46 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
}
}
/// Opaques have duplicated lifetimes and we need to compute bidirectional outlives predicates to
/// enforce that these lifetimes stay in sync.
fn compute_bidirectional_outlives_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
item_def_id: LocalDefId,
lifetime_mapping: impl Iterator<Item = (Lifetime, (LocalDefId, Symbol, Span))>,
generics: &Generics,
predicates: &mut Vec<(ty::Clause<'tcx>, Span)>,
) {
let icx = ItemCtxt::new(tcx, item_def_id);
for (arg, (dup_def, name, span)) in lifetime_mapping {
let orig_region = icx.astconv().ast_region_to_region(&arg, None);
if !matches!(orig_region.kind(), ty::ReEarlyBound(..)) {
// There is no late-bound lifetime to actually match up here, since the lifetime doesn't
// show up in the opaque's parent's substs.
continue;
}
let Some(dup_index) = generics.param_def_id_to_index(icx.tcx, dup_def.to_def_id()) else { bug!() };
let dup_region = ty::Region::new_early_bound(
tcx,
ty::EarlyBoundRegion { def_id: dup_def.to_def_id(), index: dup_index, name },
);
predicates.push((
ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(orig_region, dup_region))
.to_predicate(tcx),
span,
));
predicates.push((
ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(dup_region, orig_region))
.to_predicate(tcx),
span,
));
}
}
fn const_evaluatable_predicates_of(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
@ -668,7 +740,7 @@ pub(super) fn type_param_predicates(
ItemKind::Fn(.., generics, _)
| ItemKind::Impl(&hir::Impl { generics, .. })
| ItemKind::TyAlias(_, generics)
| ItemKind::OpaqueTy(OpaqueTy {
| ItemKind::OpaqueTy(&OpaqueTy {
generics,
origin: hir::OpaqueTyOrigin::TyAlias { .. },
..

View File

@ -556,7 +556,7 @@ fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
});
}
}
hir::ItemKind::OpaqueTy(hir::OpaqueTy {
hir::ItemKind::OpaqueTy(&hir::OpaqueTy {
origin: hir::OpaqueTyOrigin::FnReturn(parent) | hir::OpaqueTyOrigin::AsyncFn(parent),
generics,
..

View File

@ -435,7 +435,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<Ty
..
}) => opaque::find_opaque_ty_constraints_for_tait(tcx, def_id),
// Opaque types desugared from `impl Trait`.
ItemKind::OpaqueTy(OpaqueTy {
ItemKind::OpaqueTy(&OpaqueTy {
origin:
hir::OpaqueTyOrigin::FnReturn(owner) | hir::OpaqueTyOrigin::AsyncFn(owner),
in_trait,

View File

@ -340,12 +340,6 @@ fn associated_type_for_impl_trait_in_trait(
}
});
// There are no predicates for the synthesized associated type.
trait_assoc_ty.explicit_predicates_of(ty::GenericPredicates {
parent: Some(trait_def_id.to_def_id()),
predicates: &[],
});
// There are no inferred outlives for the synthesized associated type.
trait_assoc_ty.inferred_outlives_of(&[]);
@ -424,12 +418,6 @@ fn associated_type_for_impl_trait_in_impl(
}
});
// There are no predicates for the synthesized associated type.
impl_assoc_ty.explicit_predicates_of(ty::GenericPredicates {
parent: Some(impl_local_def_id.to_def_id()),
predicates: &[],
});
// There are no inferred outlives for the synthesized associated type.
impl_assoc_ty.inferred_outlives_of(&[]);

View File

@ -131,7 +131,9 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> {
if let Some(ImplTraitInTraitData::Trait { fn_def_id, .. })
| Some(ImplTraitInTraitData::Impl { fn_def_id, .. }) = tcx.opt_rpitit_info(def_id)
{
predicates = tcx.predicates_of(fn_def_id).instantiate_identity(tcx).predicates;
// FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): Should not need to add the predicates
// from the parent fn to our assumptions
predicates.extend(tcx.predicates_of(fn_def_id).instantiate_identity(tcx).predicates);
}
// Finally, we have to normalize the bounds in the environment, in

View File

@ -1,5 +1,7 @@
// check-pass
// edition: 2021
// [next] compile-flags: -Zlower-impl-trait-in-trait-to-assoc-ty
// revisions: current next
#![feature(async_fn_in_trait)]
#![allow(incomplete_features)]

View File

@ -1,5 +1,7 @@
// check-pass
// edition: 2021
// [next] compile-flags: -Zlower-impl-trait-in-trait-to-assoc-ty
// revisions: current next
#![feature(async_fn_in_trait)]
#![allow(incomplete_features)]