This commit is contained in:
Michael Goulet 2025-04-22 23:31:22 +00:00
parent 7c1661f945
commit f943f73db4
19 changed files with 189 additions and 187 deletions

View file

@ -967,7 +967,9 @@ impl<'tcx> InferCtxt<'tcx> {
pub fn can_define_opaque_ty(&self, id: impl Into<DefId>) -> bool {
debug_assert!(!self.next_trait_solver());
match self.typing_mode() {
TypingMode::Analysis { defining_opaque_types, stalled_generators: _ }
TypingMode::Analysis {
defining_opaque_types_and_generators: defining_opaque_types,
}
| TypingMode::Borrowck { defining_opaque_types } => {
id.into().as_local().is_some_and(|def_id| defining_opaque_types.contains(&def_id))
}
@ -1262,7 +1264,7 @@ impl<'tcx> InferCtxt<'tcx> {
// to handle them without proper canonicalization. This means we may cause cycle
// errors and fail to reveal opaques while inside of bodies. We should rename this
// function and require explicit comments on all use-sites in the future.
ty::TypingMode::Analysis { defining_opaque_types: _, stalled_generators: _ }
ty::TypingMode::Analysis { defining_opaque_types_and_generators: _ }
| ty::TypingMode::Borrowck { defining_opaque_types: _ } => {
TypingMode::non_body_analysis()
}

View file

@ -391,7 +391,7 @@ rustc_queries! {
key: LocalDefId
) -> &'tcx ty::List<LocalDefId> {
desc {
|tcx| "computing the opaque types defined by `{}`",
|tcx| "computing the coroutines defined within `{}`",
tcx.def_path_str(key.to_def_id())
}
}

View file

@ -366,11 +366,11 @@ macro_rules! define_callbacks {
pub type Storage<'tcx> = <$($K)* as keys::Key>::Cache<Erase<$V>>;
// Ensure that keys grow no larger than 96 bytes by accident.
// Ensure that keys grow no larger than 88 bytes by accident.
// Increase this limit if necessary, but do try to keep the size low if possible
#[cfg(target_pointer_width = "64")]
const _: () = {
if size_of::<Key<'static>>() > 96 {
if size_of::<Key<'static>>() > 88 {
panic!("{}", concat!(
"the query `",
stringify!($name),

View file

@ -678,11 +678,18 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.opaque_types_defined_by(defining_anchor)
}
fn stalled_generators_within(self, defining_anchor: Self::LocalDefId) -> Self::LocalDefIds {
fn opaque_types_and_generators_defined_by(
self,
defining_anchor: Self::LocalDefId,
) -> Self::LocalDefIds {
if self.next_trait_solver_globally() {
self.stalled_generators_within(defining_anchor)
self.mk_local_def_ids_from_iter(
self.opaque_types_defined_by(defining_anchor)
.iter()
.chain(self.stalled_generators_within(defining_anchor)),
)
} else {
ty::List::empty()
self.opaque_types_defined_by(defining_anchor)
}
}
}
@ -2914,11 +2921,11 @@ impl<'tcx> TyCtxt<'tcx> {
self.interners.intern_clauses(clauses)
}
pub fn mk_local_def_ids(self, clauses: &[LocalDefId]) -> &'tcx List<LocalDefId> {
pub fn mk_local_def_ids(self, def_ids: &[LocalDefId]) -> &'tcx List<LocalDefId> {
// FIXME consider asking the input slice to be sorted to avoid
// re-interning permutations, in which case that would be asserted
// here.
self.intern_local_def_ids(clauses)
self.intern_local_def_ids(def_ids)
}
pub fn mk_local_def_ids_from_iter<I, T>(self, iter: I) -> T::Output

View file

@ -329,10 +329,7 @@ where
TypingMode::Coherence | TypingMode::PostAnalysis => false,
// During analysis, opaques are rigid unless they may be defined by
// the current body.
TypingMode::Analysis {
defining_opaque_types: non_rigid_opaques,
stalled_generators: _,
}
TypingMode::Analysis { defining_opaque_types_and_generators: non_rigid_opaques }
| TypingMode::Borrowck { defining_opaque_types: non_rigid_opaques }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: non_rigid_opaques } => {
!def_id.as_local().is_some_and(|def_id| non_rigid_opaques.contains(&def_id))

View file

@ -33,11 +33,11 @@ where
);
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
}
TypingMode::Analysis { defining_opaque_types, stalled_generators: _ } => {
TypingMode::Analysis { defining_opaque_types_and_generators } => {
let Some(def_id) = opaque_ty
.def_id
.as_local()
.filter(|&def_id| defining_opaque_types.contains(&def_id))
.filter(|&def_id| defining_opaque_types_and_generators.contains(&def_id))
else {
self.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
return self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);

View file

@ -208,20 +208,9 @@ where
}
}
// TODO:
if let ty::CoroutineWitness(def_id, _) = goal.predicate.self_ty().kind() {
match ecx.typing_mode() {
TypingMode::Analysis { stalled_generators, defining_opaque_types: _ } => {
if def_id.as_local().is_some_and(|def_id| stalled_generators.contains(&def_id))
{
return ecx.forced_ambiguity(MaybeCause::Ambiguity);
}
}
TypingMode::Coherence
| TypingMode::PostAnalysis
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } => {}
}
// We need to make sure to stall any coroutines we are inferring to avoid query cycles.
if let Some(cand) = ecx.try_stall_coroutine_witness(goal.predicate.self_ty()) {
return cand;
}
ecx.probe_and_evaluate_goal_for_constituent_tys(
@ -275,20 +264,9 @@ where
return Err(NoSolution);
}
// TODO:
if let ty::CoroutineWitness(def_id, _) = goal.predicate.self_ty().kind() {
match ecx.typing_mode() {
TypingMode::Analysis { stalled_generators, defining_opaque_types: _ } => {
if def_id.as_local().is_some_and(|def_id| stalled_generators.contains(&def_id))
{
return ecx.forced_ambiguity(MaybeCause::Ambiguity);
}
}
TypingMode::Coherence
| TypingMode::PostAnalysis
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } => {}
}
// We need to make sure to stall any coroutines we are inferring to avoid query cycles.
if let Some(cand) = ecx.try_stall_coroutine_witness(goal.predicate.self_ty()) {
return cand;
}
ecx.probe_and_evaluate_goal_for_constituent_tys(
@ -1400,4 +1378,28 @@ where
let candidates = self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
self.merge_trait_candidates(goal, candidates)
}
fn try_stall_coroutine_witness(
&mut self,
self_ty: I::Ty,
) -> Option<Result<Candidate<I>, NoSolution>> {
if let ty::CoroutineWitness(def_id, _) = self_ty.kind() {
match self.typing_mode() {
TypingMode::Analysis {
defining_opaque_types_and_generators: stalled_generators,
} => {
if def_id.as_local().is_some_and(|def_id| stalled_generators.contains(&def_id))
{
return Some(self.forced_ambiguity(MaybeCause::Ambiguity));
}
}
TypingMode::Coherence
| TypingMode::PostAnalysis
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } => {}
}
}
None
}
}

View file

@ -14,6 +14,7 @@ use rustc_middle::ty::{
};
use rustc_next_trait_solver::solve::{GenerateProofTree, HasChanged, SolverDelegateEvalExt as _};
use rustc_span::Span;
use rustc_type_ir::data_structures::DelayedSet;
use tracing::instrument;
use self::derive_errors::*;
@ -217,26 +218,30 @@ where
&mut self,
infcx: &InferCtxt<'tcx>,
) -> PredicateObligations<'tcx> {
self.obligations.drain_pending(|obl| {
let stalled_generators = match infcx.typing_mode() {
TypingMode::Analysis { defining_opaque_types: _, stalled_generators } => {
stalled_generators
}
TypingMode::Coherence
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ }
| TypingMode::PostAnalysis => return false,
};
if stalled_generators.is_empty() {
return false;
let stalled_generators = match infcx.typing_mode() {
TypingMode::Analysis { defining_opaque_types_and_generators } => {
defining_opaque_types_and_generators
}
TypingMode::Coherence
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ }
| TypingMode::PostAnalysis => return Default::default(),
};
if stalled_generators.is_empty() {
return Default::default();
}
self.obligations.drain_pending(|obl| {
infcx.probe(|_| {
infcx
.visit_proof_tree(
obl.as_goal(),
&mut StalledOnCoroutines { stalled_generators, span: obl.cause.span },
&mut StalledOnCoroutines {
stalled_generators,
span: obl.cause.span,
cache: Default::default(),
},
)
.is_break()
})
@ -244,10 +249,18 @@ where
}
}
/// Detect if a goal is stalled on a coroutine that is owned by the current typeck root.
///
/// This function can (erroneously) fail to detect a predicate, i.e. it doesn't need to
/// be complete. However, this will lead to ambiguity errors, so we want to make it
/// accurate.
///
/// This function can be also return false positives, which will lead to poor diagnostics
/// so we want to keep this visitor *precise* too.
struct StalledOnCoroutines<'tcx> {
stalled_generators: &'tcx ty::List<LocalDefId>,
span: Span,
// TODO: Cache
cache: DelayedSet<Ty<'tcx>>,
}
impl<'tcx> inspect::ProofTreeVisitor<'tcx> for StalledOnCoroutines<'tcx> {
@ -272,6 +285,10 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for StalledOnCoroutines<'tcx> {
type Result = ControlFlow<()>;
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
if !self.cache.insert(ty) {
return ControlFlow::Continue(());
}
if let ty::CoroutineWitness(def_id, _) = *ty.kind()
&& def_id.as_local().is_some_and(|def_id| self.stalled_generators.contains(&def_id))
{

View file

@ -1,6 +1,5 @@
use std::assert_matches::assert_matches;
use std::fmt::Debug;
use std::marker::PhantomData;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_infer::infer::InferCtxt;
@ -60,7 +59,8 @@ where
/// entered before passing `value` to the function. This is currently needed for
/// `normalize_erasing_regions`, which skips binders as it walks through a type.
///
/// TODO: doc
/// This returns a set of stalled obligations if the typing mode of the underlying infcx
/// has any stalled coroutine def ids.
pub fn deeply_normalize_with_skipped_universes_and_ambiguous_goals<'tcx, T, E>(
at: At<'_, 'tcx>,
value: T,
@ -72,16 +72,10 @@ where
{
let fulfill_cx = FulfillmentCtxt::new(at.infcx);
let mut folder =
NormalizationFolder { at, fulfill_cx, depth: 0, universes, _errors: PhantomData };
NormalizationFolder { at, fulfill_cx, depth: 0, universes, stalled_goals: vec![] };
let value = value.try_fold_with(&mut folder)?;
let goals = folder
.fulfill_cx
.drain_stalled_obligations_for_coroutines(at.infcx)
.into_iter()
.map(|obl| obl.as_goal())
.collect();
let errors = folder.fulfill_cx.select_all_or_error(at.infcx);
if errors.is_empty() { Ok((value, goals)) } else { Err(errors) }
if errors.is_empty() { Ok((value, folder.stalled_goals)) } else { Err(errors) }
}
struct NormalizationFolder<'me, 'tcx, E> {
@ -89,7 +83,7 @@ struct NormalizationFolder<'me, 'tcx, E> {
fulfill_cx: FulfillmentCtxt<'tcx, E>,
depth: usize,
universes: Vec<Option<UniverseIndex>>,
_errors: PhantomData<E>,
stalled_goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
}
impl<'tcx, E> NormalizationFolder<'_, 'tcx, E>
@ -130,10 +124,7 @@ where
);
self.fulfill_cx.register_predicate_obligation(infcx, obligation);
let errors = self.fulfill_cx.select_where_possible(infcx);
if !errors.is_empty() {
return Err(errors);
}
self.select_all_and_stall_coroutine_predicates()?;
// Alias is guaranteed to be fully structurally resolved,
// so we can super fold here.
@ -184,6 +175,27 @@ where
self.depth -= 1;
Ok(result)
}
fn select_all_and_stall_coroutine_predicates(&mut self) -> Result<(), Vec<E>> {
let errors = self.fulfill_cx.select_where_possible(self.at.infcx);
if !errors.is_empty() {
return Err(errors);
}
self.stalled_goals.extend(
self.fulfill_cx
.drain_stalled_obligations_for_coroutines(self.at.infcx)
.into_iter()
.map(|obl| obl.as_goal()),
);
let errors = self.fulfill_cx.collect_remaining_errors(self.at.infcx);
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
}
impl<'tcx, E> FallibleTypeFolder<TyCtxt<'tcx>> for NormalizationFolder<'_, 'tcx, E>

View file

@ -1498,7 +1498,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// However, if we disqualify *all* goals from being cached, perf suffers.
// This is likely fixed by better caching in general in the new solver.
// See: <https://github.com/rust-lang/rust/issues/132064>.
TypingMode::Analysis { defining_opaque_types, stalled_generators: _ }
TypingMode::Analysis {
defining_opaque_types_and_generators: defining_opaque_types,
}
| TypingMode::Borrowck { defining_opaque_types } => {
defining_opaque_types.is_empty() || !pred.has_opaque_types()
}

View file

@ -32,6 +32,7 @@ mod needs_drop;
mod opaque_types;
mod representability;
pub mod sig_types;
mod stalled_generators;
mod structural_match;
mod ty;
@ -50,4 +51,5 @@ pub fn provide(providers: &mut Providers) {
ty::provide(providers);
instance::provide(providers);
structural_match::provide(providers);
stalled_generators::provide(providers);
}

View file

@ -1,7 +1,6 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit;
use rustc_hir::intravisit::Visitor;
use rustc_middle::query::Providers;
@ -356,51 +355,6 @@ fn opaque_types_defined_by<'tcx>(
tcx.mk_local_def_ids(&collector.opaques)
}
// TODO: Move this out of `opaque_types`
fn stalled_generators_within<'tcx>(
tcx: TyCtxt<'tcx>,
item: LocalDefId,
) -> &'tcx ty::List<LocalDefId> {
if !tcx.next_trait_solver_globally() {
return ty::List::empty();
}
let body = tcx.hir_body_owned_by(item);
let mut collector =
StalledGeneratorVisitor { tcx, root_def_id: item.to_def_id(), stalled_coroutines: vec![] };
collector.visit_body(body);
tcx.mk_local_def_ids(&collector.stalled_coroutines)
}
struct StalledGeneratorVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
root_def_id: DefId,
stalled_coroutines: Vec<LocalDefId>,
}
impl<'tcx> Visitor<'tcx> for StalledGeneratorVisitor<'tcx> {
fn visit_nested_body(&mut self, id: hir::BodyId) {
if self.tcx.typeck_root_def_id(self.tcx.hir_body_owner_def_id(id).to_def_id())
== self.root_def_id
{
let body = self.tcx.hir_body(id);
self.visit_body(body);
}
}
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
if let hir::ExprKind::Closure(&hir::Closure {
def_id,
kind: hir::ClosureKind::Coroutine(_),
..
}) = ex.kind
{
self.stalled_coroutines.push(def_id);
}
intravisit::walk_expr(self, ex);
}
}
pub(super) fn provide(providers: &mut Providers) {
*providers = Providers { opaque_types_defined_by, stalled_generators_within, ..*providers };
*providers = Providers { opaque_types_defined_by, ..*providers };
}

View file

@ -0,0 +1,54 @@
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit;
use rustc_hir::intravisit::Visitor;
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, TyCtxt};
fn stalled_generators_within<'tcx>(
tcx: TyCtxt<'tcx>,
item: LocalDefId,
) -> &'tcx ty::List<LocalDefId> {
if !tcx.next_trait_solver_globally() {
return ty::List::empty();
}
let body = tcx.hir_body_owned_by(item);
let mut collector =
StalledGeneratorVisitor { tcx, root_def_id: item.to_def_id(), stalled_coroutines: vec![] };
collector.visit_body(body);
tcx.mk_local_def_ids(&collector.stalled_coroutines)
}
struct StalledGeneratorVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
root_def_id: DefId,
stalled_coroutines: Vec<LocalDefId>,
}
impl<'tcx> Visitor<'tcx> for StalledGeneratorVisitor<'tcx> {
fn visit_nested_body(&mut self, id: hir::BodyId) {
if self.tcx.typeck_root_def_id(self.tcx.hir_body_owner_def_id(id).to_def_id())
== self.root_def_id
{
let body = self.tcx.hir_body(id);
self.visit_body(body);
}
}
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
if let hir::ExprKind::Closure(&hir::Closure {
def_id,
kind: hir::ClosureKind::Coroutine(_),
..
}) = ex.kind
{
self.stalled_coroutines.push(def_id);
}
intravisit::walk_expr(self, ex);
}
}
pub(super) fn provide(providers: &mut Providers) {
*providers = Providers { stalled_generators_within, ..*providers };
}

View file

@ -65,7 +65,7 @@ pub enum TypingMode<I: Interner> {
/// let x: <() as Assoc>::Output = true;
/// }
/// ```
Analysis { defining_opaque_types: I::LocalDefIds, stalled_generators: I::LocalDefIds },
Analysis { defining_opaque_types_and_generators: I::LocalDefIds },
/// The behavior during MIR borrowck is identical to `TypingMode::Analysis`
/// except that the initial value for opaque types is the type computed during
/// HIR typeck with unique unconstrained region inference variables.
@ -94,25 +94,24 @@ pub enum TypingMode<I: Interner> {
impl<I: Interner> TypingMode<I> {
/// Analysis outside of a body does not define any opaque types.
pub fn non_body_analysis() -> TypingMode<I> {
TypingMode::Analysis {
defining_opaque_types: Default::default(),
stalled_generators: Default::default(),
}
TypingMode::Analysis { defining_opaque_types_and_generators: Default::default() }
}
pub fn typeck_for_body(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {
TypingMode::Analysis {
defining_opaque_types: cx.opaque_types_defined_by(body_def_id),
stalled_generators: cx.stalled_generators_within(body_def_id),
defining_opaque_types_and_generators: cx
.opaque_types_and_generators_defined_by(body_def_id),
}
}
/// While typechecking a body, we need to be able to define the opaque
/// types defined by that body.
///
/// FIXME: This will be removed because it's generally not correct to define
/// opaques outside of HIR typeck.
pub fn analysis_in_body(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {
TypingMode::Analysis {
defining_opaque_types: cx.opaque_types_defined_by(body_def_id),
stalled_generators: Default::default(),
defining_opaque_types_and_generators: cx.opaque_types_defined_by(body_def_id),
}
}

View file

@ -332,7 +332,10 @@ pub trait Interner:
fn opaque_types_defined_by(self, defining_anchor: Self::LocalDefId) -> Self::LocalDefIds;
fn stalled_generators_within(self, defining_anchor: Self::LocalDefId) -> Self::LocalDefIds;
fn opaque_types_and_generators_defined_by(
self,
defining_anchor: Self::LocalDefId,
) -> Self::LocalDefIds;
}
/// Imagine you have a function `F: FnOnce(&[T]) -> R`, plus an iterator `iter`

View file

@ -1,13 +1,11 @@
error[E0271]: expected `{async closure@is-not-fn.rs:8:14}` to return `()`, but it returns `{async closure body@$DIR/is-not-fn.rs:8:23: 8:25}`
error[E0271]: type mismatch resolving `{async closure body@$DIR/is-not-fn.rs:8:23: 8:25} == ()`
--> $DIR/is-not-fn.rs:8:14
|
LL | needs_fn(async || {});
| -------- ^^^^^^^^^^^ expected `()`, found `async` closure body
| -------- ^^^^^^^^^^^ types differ
| |
| required by a bound introduced by this call
|
= note: expected unit type `()`
found `async` closure body `{async closure body@$DIR/is-not-fn.rs:8:23: 8:25}`
note: required by a bound in `needs_fn`
--> $DIR/is-not-fn.rs:7:25
|

View file

@ -6,5 +6,6 @@
fn main() {
fn needs_fn(x: impl FnOnce()) {}
needs_fn(async || {});
//~^ ERROR expected `{async closure@is-not-fn.rs:8:14}` to return `()`
//[current]~^ ERROR expected `{async closure@is-not-fn.rs:8:14}` to return `()`
//[next]~^^ ERROR type mismatch resolving `{async closure body@$DIR/is-not-fn.rs:8:23: 8:25} == ()`
}

View file

@ -1,47 +0,0 @@
error[E0391]: cycle detected when type-checking `foo`
--> $DIR/clone-rpit.rs:13:1
|
LL | pub fn foo<'a, 'b>() -> impl Clone {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...which requires coroutine witness types for `foo::{closure#0}`...
--> $DIR/clone-rpit.rs:15:5
|
LL | move |_: ()| {
| ^^^^^^^^^^^^
note: ...which requires promoting constants in MIR for `foo::{closure#0}`...
--> $DIR/clone-rpit.rs:15:5
|
LL | move |_: ()| {
| ^^^^^^^^^^^^
note: ...which requires checking if `foo::{closure#0}` contains FFI-unwind calls...
--> $DIR/clone-rpit.rs:15:5
|
LL | move |_: ()| {
| ^^^^^^^^^^^^
note: ...which requires building MIR for `foo::{closure#0}`...
--> $DIR/clone-rpit.rs:15:5
|
LL | move |_: ()| {
| ^^^^^^^^^^^^
note: ...which requires match-checking `foo::{closure#0}`...
--> $DIR/clone-rpit.rs:15:5
|
LL | move |_: ()| {
| ^^^^^^^^^^^^
note: ...which requires type-checking `foo::{closure#0}`...
--> $DIR/clone-rpit.rs:15:5
|
LL | move |_: ()| {
| ^^^^^^^^^^^^
= note: ...which again requires type-checking `foo`, completing the cycle
note: cycle used when match-checking `foo`
--> $DIR/clone-rpit.rs:13:1
|
LL | pub fn foo<'a, 'b>() -> impl Clone {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0391`.

View file

@ -1,8 +1,7 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
//@[current] check-pass
//@[next] known-bug: trait-system-refactor-initiative#82
//@ check-pass
#![feature(coroutines, coroutine_trait, coroutine_clone)]