Rollup merge of #129072 - compiler-errors:more-powerful-async-closure-inference, r=lcnr

Infer async closure args from `Fn` bound even if there is no corresponding `Future` bound on return

In #127482, I implemented the functionality to infer an async closure signature when passed into a function that has `Fn` + `Future` where clauses that look like:

```
fn whatever(callback: F)
where
  F: Fn(Arg) -> Fut,
  Fut: Future<Output = Out>,
```

However, #127781 demonstrates that this is still incomplete to address the cases users care about. So let's not bail when we fail to find a `Future` bound, and try our best to just use the args from the `Fn` bound if we find it. This is *fine* since most users of closures only really care about the *argument* types for inference guidance, since we require the receiver of a `.` method call to be known in order to probe methods.

When I experimented with programmatically rewriting `|| async {}` to `async || {}` in #127827, this also seems to have fixed ~5000 regressions (probably all coming from usages `TryFuture`/`TryStream` from futures-rs): the [before](https://github.com/rust-lang/rust/pull/127827#issuecomment-2254061733) and [after](https://github.com/rust-lang/rust/pull/127827#issuecomment-2255470176) crater runs.

Fixes #127781.
This commit is contained in:
Matthias Krüger 2024-08-15 19:32:36 +02:00 committed by GitHub
commit 53bf554de8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 11 deletions

View File

@ -14,7 +14,7 @@
use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt};
use rustc_middle::ty::{self, GenericArgs, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor}; use rustc_middle::ty::{self, GenericArgs, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor};
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::Span; use rustc_span::{Span, DUMMY_SP};
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use rustc_trait_selection::error_reporting::traits::ArgKind; use rustc_trait_selection::error_reporting::traits::ArgKind;
use rustc_trait_selection::traits; use rustc_trait_selection::traits;
@ -539,6 +539,10 @@ fn extract_sig_from_projection(
/// we identify the `FnOnce<Args, Output = ?Fut>` bound, and if the output type is /// we identify the `FnOnce<Args, Output = ?Fut>` bound, and if the output type is
/// an inference variable `?Fut`, we check if that is bounded by a `Future<Output = Ty>` /// an inference variable `?Fut`, we check if that is bounded by a `Future<Output = Ty>`
/// projection. /// projection.
///
/// This function is actually best-effort with the return type; if we don't find a
/// `Future` projection, we still will return arguments that we extracted from the `FnOnce`
/// projection, and the output will be an unconstrained type variable instead.
fn extract_sig_from_projection_and_future_bound( fn extract_sig_from_projection_and_future_bound(
&self, &self,
cause_span: Option<Span>, cause_span: Option<Span>,
@ -564,24 +568,43 @@ fn extract_sig_from_projection_and_future_bound(
}; };
// FIXME: We may want to elaborate here, though I assume this will be exceedingly rare. // FIXME: We may want to elaborate here, though I assume this will be exceedingly rare.
let mut return_ty = None;
for bound in self.obligations_for_self_ty(return_vid) { for bound in self.obligations_for_self_ty(return_vid) {
if let Some(ret_projection) = bound.predicate.as_projection_clause() if let Some(ret_projection) = bound.predicate.as_projection_clause()
&& let Some(ret_projection) = ret_projection.no_bound_vars() && let Some(ret_projection) = ret_projection.no_bound_vars()
&& self.tcx.is_lang_item(ret_projection.def_id(), LangItem::FutureOutput) && self.tcx.is_lang_item(ret_projection.def_id(), LangItem::FutureOutput)
{ {
let sig = projection.rebind(self.tcx.mk_fn_sig( return_ty = Some(ret_projection.term.expect_type());
input_tys, break;
ret_projection.term.expect_type(),
false,
hir::Safety::Safe,
Abi::Rust,
));
return Some(ExpectedSig { cause_span, sig });
} }
} }
None // SUBTLE: If we didn't find a `Future<Output = ...>` bound for the return
// vid, we still want to attempt to provide inference guidance for the async
// closure's arguments. Instantiate a new vid to plug into the output type.
//
// You may be wondering, what if it's higher-ranked? Well, given that we
// found a type variable for the `FnOnce::Output` projection above, we know
// that the output can't mention any of the vars.
//
// Also note that we use a fresh var here for the signature since the signature
// records the output of the *future*, and `return_vid` above is the type
// variable of the future, not its output.
//
// FIXME: We probably should store this signature inference output in a way
// that does not misuse a `FnSig` type, but that can be done separately.
let return_ty =
return_ty.unwrap_or_else(|| self.next_ty_var(cause_span.unwrap_or(DUMMY_SP)));
let sig = projection.rebind(self.tcx.mk_fn_sig(
input_tys,
return_ty,
false,
hir::Safety::Safe,
Abi::Rust,
));
return Some(ExpectedSig { cause_span, sig });
} }
fn sig_of_closure( fn sig_of_closure(

View File

@ -0,0 +1,49 @@
//@ check-pass
//@ edition: 2021
// Make sure that we infer the args of an async closure even if it's passed to
// a function that requires the async closure implement `Fn*` but does *not* have
// a `Future` bound on the return type.
#![feature(async_closure)]
use std::future::Future;
trait TryStream {
type Ok;
type Err;
}
trait TryFuture {
type Ok;
type Err;
}
impl<F, T, E> TryFuture for F where F: Future<Output = Result<T, E>> {
type Ok = T;
type Err = E;
}
trait TryStreamExt: TryStream {
fn try_for_each<F, Fut>(&self, f: F)
where
F: FnMut(Self::Ok) -> Fut,
Fut: TryFuture<Ok = (), Err = Self::Err>;
}
impl<S> TryStreamExt for S where S: TryStream {
fn try_for_each<F, Fut>(&self, f: F)
where
F: FnMut(Self::Ok) -> Fut,
Fut: TryFuture<Ok = (), Err = Self::Err>,
{ }
}
fn test(stream: impl TryStream<Ok = &'static str, Err = ()>) {
stream.try_for_each(async |s| {
s.trim(); // Make sure we know the type of `s` at this point.
Ok(())
});
}
fn main() {}