Skip to content

Commit 6fca8c8

Browse files
authored
Unrolled build for rust-lang#125259
Rollup merge of rust-lang#125259 - compiler-errors:fn-mut-as-a-treat, r=oli-obk An async closure may implement `FnMut`/`Fn` if it has no self-borrows There's no reason that async closures may not implement `FnMut` or `Fn` if they don't actually borrow anything with the closure's env lifetime. Specifically, rust-lang#123660 made it so that we don't always need to borrow captures from the closure's env. See the doc comment on `should_reborrow_from_env_of_parent_coroutine_closure`: https://github.com/rust-lang/rust/blob/c00957a3e269219413041a4e3565f33b1f9d0779/compiler/rustc_hir_typeck/src/upvar.rs#L1777-L1823 If there are no such borrows, then we are free to implement `FnMut` and `Fn` as permitted by our closure's inferred `ClosureKind`. As far as I can tell, this change makes `async || {}` work in precisely the set of places they used to work before rust-lang#120361. Fixes rust-lang#125247. r? oli-obk
2 parents 22f5bdc + 2e97dae commit 6fca8c8

File tree

4 files changed

+70
-20
lines changed

4 files changed

+70
-20
lines changed

compiler/rustc_middle/src/ty/sty.rs

+39
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,45 @@ impl<'tcx> CoroutineClosureArgs<'tcx> {
401401
pub fn coroutine_witness_ty(self) -> Ty<'tcx> {
402402
self.split().coroutine_witness_ty
403403
}
404+
405+
pub fn has_self_borrows(&self) -> bool {
406+
match self.coroutine_captures_by_ref_ty().kind() {
407+
ty::FnPtr(sig) => sig
408+
.skip_binder()
409+
.visit_with(&mut HasRegionsBoundAt { binder: ty::INNERMOST })
410+
.is_break(),
411+
ty::Error(_) => true,
412+
_ => bug!(),
413+
}
414+
}
415+
}
416+
/// Unlike `has_escaping_bound_vars` or `outermost_exclusive_binder`, this will
417+
/// detect only regions bound *at* the debruijn index.
418+
struct HasRegionsBoundAt {
419+
binder: ty::DebruijnIndex,
420+
}
421+
// FIXME: Could be optimized to not walk into components with no escaping bound vars.
422+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for HasRegionsBoundAt {
423+
type Result = ControlFlow<()>;
424+
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
425+
&mut self,
426+
t: &ty::Binder<'tcx, T>,
427+
) -> Self::Result {
428+
self.binder.shift_in(1);
429+
t.super_visit_with(self)?;
430+
self.binder.shift_out(1);
431+
ControlFlow::Continue(())
432+
}
433+
434+
fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result {
435+
if let ty::ReBound(binder, _) = *r
436+
&& self.binder == binder
437+
{
438+
ControlFlow::Break(())
439+
} else {
440+
ControlFlow::Continue(())
441+
}
442+
}
404443
}
405444

406445
#[derive(Copy, Clone, PartialEq, Eq, Debug, TypeFoldable, TypeVisitable)]

compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,11 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
300300
return Err(NoSolution);
301301
}
302302

303-
// If `Fn`/`FnMut`, we only implement this goal if we
304-
// have no captures.
305-
let no_borrows = match args.tupled_upvars_ty().kind() {
306-
ty::Tuple(tys) => tys.is_empty(),
307-
ty::Error(_) => false,
308-
_ => bug!("tuple_fields called on non-tuple"),
309-
};
310-
if closure_kind != ty::ClosureKind::FnOnce && !no_borrows {
303+
// A coroutine-closure implements `FnOnce` *always*, since it may
304+
// always be called once. It additionally implements `Fn`/`FnMut`
305+
// only if it has no upvars referencing the closure-env lifetime,
306+
// and if the closure kind permits it.
307+
if closure_kind != ty::ClosureKind::FnOnce && args.has_self_borrows() {
311308
return Err(NoSolution);
312309
}
313310

compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+3-12
Original file line numberDiff line numberDiff line change
@@ -418,20 +418,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
418418
// Ambiguity if upvars haven't been constrained yet
419419
&& !args.tupled_upvars_ty().is_ty_var()
420420
{
421-
let no_borrows = match args.tupled_upvars_ty().kind() {
422-
ty::Tuple(tys) => tys.is_empty(),
423-
ty::Error(_) => false,
424-
_ => bug!("tuple_fields called on non-tuple"),
425-
};
426421
// A coroutine-closure implements `FnOnce` *always*, since it may
427422
// always be called once. It additionally implements `Fn`/`FnMut`
428-
// only if it has no upvars (therefore no borrows from the closure
429-
// that would need to be represented with a lifetime) and if the
430-
// closure kind permits it.
431-
// FIXME(async_closures): Actually, it could also implement `Fn`/`FnMut`
432-
// if it takes all of its upvars by copy, and none by ref. This would
433-
// require us to record a bit more information during upvar analysis.
434-
if no_borrows && closure_kind.extends(kind) {
423+
// only if it has no upvars referencing the closure-env lifetime,
424+
// and if the closure kind permits it.
425+
if closure_kind.extends(kind) && !args.has_self_borrows() {
435426
candidates.vec.push(ClosureCandidate { is_const });
436427
} else if kind == ty::ClosureKind::FnOnce {
437428
candidates.vec.push(ClosureCandidate { is_const });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@ check-pass
2+
//@ edition: 2021
3+
4+
// Demonstrates that an async closure may implement `FnMut` (not just `async FnMut`!)
5+
// if it has no self-borrows. In this case, `&Ty` is not borrowed from the closure env,
6+
// since it's fine to reborrow it with its original lifetime. See the doc comment on
7+
// `should_reborrow_from_env_of_parent_coroutine_closure` for more detail for when we
8+
// must borrow from the closure env.
9+
10+
#![feature(async_closure)]
11+
12+
fn main() {}
13+
14+
fn needs_fn_mut<T>(x: impl FnMut() -> T) {}
15+
16+
fn hello(x: &Ty) {
17+
needs_fn_mut(async || { x.hello(); });
18+
}
19+
20+
struct Ty;
21+
impl Ty {
22+
fn hello(&self) {}
23+
}

0 commit comments

Comments
 (0)