From 2e97dae8d468623474d05dd84c270583ec3ed374 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 18 May 2024 12:40:36 -0400 Subject: [PATCH] An async closure may implement FnMut/Fn if it has no self-borrows --- compiler/rustc_middle/src/ty/sty.rs | 39 +++++++++++++++++++ .../src/solve/assembly/structural_traits.rs | 13 +++---- .../src/traits/select/candidate_assembly.rs | 15 ++----- .../async-closures/implements-fnmut.rs | 23 +++++++++++ 4 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 tests/ui/async-await/async-closures/implements-fnmut.rs diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 9dbcd938e6eda..6526bed5bdd63 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -401,6 +401,45 @@ impl<'tcx> CoroutineClosureArgs<'tcx> { pub fn coroutine_witness_ty(self) -> Ty<'tcx> { self.split().coroutine_witness_ty } + + pub fn has_self_borrows(&self) -> bool { + match self.coroutine_captures_by_ref_ty().kind() { + ty::FnPtr(sig) => sig + .skip_binder() + .visit_with(&mut HasRegionsBoundAt { binder: ty::INNERMOST }) + .is_break(), + ty::Error(_) => true, + _ => bug!(), + } + } +} +/// Unlike `has_escaping_bound_vars` or `outermost_exclusive_binder`, this will +/// detect only regions bound *at* the debruijn index. +struct HasRegionsBoundAt { + binder: ty::DebruijnIndex, +} +// FIXME: Could be optimized to not walk into components with no escaping bound vars. +impl<'tcx> TypeVisitor> for HasRegionsBoundAt { + type Result = ControlFlow<()>; + fn visit_binder>>( + &mut self, + t: &ty::Binder<'tcx, T>, + ) -> Self::Result { + self.binder.shift_in(1); + t.super_visit_with(self)?; + self.binder.shift_out(1); + ControlFlow::Continue(()) + } + + fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result { + if let ty::ReBound(binder, _) = *r + && self.binder == binder + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + } } #[derive(Copy, Clone, PartialEq, Eq, Debug, TypeFoldable, TypeVisitable)] diff --git a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs index 90cc33e0275b6..f5e397f12d2da 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs @@ -300,14 +300,11 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>( return Err(NoSolution); } - // If `Fn`/`FnMut`, we only implement this goal if we - // have no captures. - let no_borrows = match args.tupled_upvars_ty().kind() { - ty::Tuple(tys) => tys.is_empty(), - ty::Error(_) => false, - _ => bug!("tuple_fields called on non-tuple"), - }; - if closure_kind != ty::ClosureKind::FnOnce && !no_borrows { + // A coroutine-closure implements `FnOnce` *always*, since it may + // always be called once. It additionally implements `Fn`/`FnMut` + // only if it has no upvars referencing the closure-env lifetime, + // and if the closure kind permits it. + if closure_kind != ty::ClosureKind::FnOnce && args.has_self_borrows() { return Err(NoSolution); } diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index b9e853a067874..fd7c47ad6fb3d 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -418,20 +418,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // Ambiguity if upvars haven't been constrained yet && !args.tupled_upvars_ty().is_ty_var() { - let no_borrows = match args.tupled_upvars_ty().kind() { - ty::Tuple(tys) => tys.is_empty(), - ty::Error(_) => false, - _ => bug!("tuple_fields called on non-tuple"), - }; // A coroutine-closure implements `FnOnce` *always*, since it may // always be called once. It additionally implements `Fn`/`FnMut` - // only if it has no upvars (therefore no borrows from the closure - // that would need to be represented with a lifetime) and if the - // closure kind permits it. - // FIXME(async_closures): Actually, it could also implement `Fn`/`FnMut` - // if it takes all of its upvars by copy, and none by ref. This would - // require us to record a bit more information during upvar analysis. - if no_borrows && closure_kind.extends(kind) { + // only if it has no upvars referencing the closure-env lifetime, + // and if the closure kind permits it. + if closure_kind.extends(kind) && !args.has_self_borrows() { candidates.vec.push(ClosureCandidate { is_const }); } else if kind == ty::ClosureKind::FnOnce { candidates.vec.push(ClosureCandidate { is_const }); diff --git a/tests/ui/async-await/async-closures/implements-fnmut.rs b/tests/ui/async-await/async-closures/implements-fnmut.rs new file mode 100644 index 0000000000000..1ed326cd0618b --- /dev/null +++ b/tests/ui/async-await/async-closures/implements-fnmut.rs @@ -0,0 +1,23 @@ +//@ check-pass +//@ edition: 2021 + +// Demonstrates that an async closure may implement `FnMut` (not just `async FnMut`!) +// if it has no self-borrows. In this case, `&Ty` is not borrowed from the closure env, +// since it's fine to reborrow it with its original lifetime. See the doc comment on +// `should_reborrow_from_env_of_parent_coroutine_closure` for more detail for when we +// must borrow from the closure env. + +#![feature(async_closure)] + +fn main() {} + +fn needs_fn_mut(x: impl FnMut() -> T) {} + +fn hello(x: &Ty) { + needs_fn_mut(async || { x.hello(); }); +} + +struct Ty; +impl Ty { + fn hello(&self) {} +}