about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-08-15 19:32:36 +0200
committerGitHub <noreply@github.com>2024-08-15 19:32:36 +0200
commit53bf554de82d64daecf0cf2696cf4da367a96e69 (patch)
tree7e9c4c5b2c9dcd61cc43a20d2ebd13644fd710df
parentbb63d75ac1477eb361bfee764709f5d54e08cec8 (diff)
parent4290943fb35b1e0472d6ee4888c5ab106abe1311 (diff)
downloadrust-53bf554de82d64daecf0cf2696cf4da367a96e69.tar.gz
rust-53bf554de82d64daecf0cf2696cf4da367a96e69.zip
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.
-rw-r--r--compiler/rustc_hir_typeck/src/closure.rs45
-rw-r--r--tests/ui/async-await/async-closures/sig-from-bare-fn.rs49
2 files changed, 83 insertions, 11 deletions
diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs
index 589a9c5a719..6b813dc64ce 100644
--- a/compiler/rustc_hir_typeck/src/closure.rs
+++ b/compiler/rustc_hir_typeck/src/closure.rs
@@ -14,7 +14,7 @@ use rustc_middle::span_bug;
 use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt};
 use rustc_middle::ty::{self, GenericArgs, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor};
 use rustc_span::def_id::LocalDefId;
-use rustc_span::Span;
+use rustc_span::{Span, DUMMY_SP};
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::error_reporting::traits::ArgKind;
 use rustc_trait_selection::traits;
@@ -539,6 +539,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// 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>`
     /// 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(
         &self,
         cause_span: Option<Span>,
@@ -564,24 +568,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         };
 
         // 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) {
             if let Some(ret_projection) = bound.predicate.as_projection_clause()
                 && let Some(ret_projection) = ret_projection.no_bound_vars()
                 && self.tcx.is_lang_item(ret_projection.def_id(), LangItem::FutureOutput)
             {
-                let sig = projection.rebind(self.tcx.mk_fn_sig(
-                    input_tys,
-                    ret_projection.term.expect_type(),
-                    false,
-                    hir::Safety::Safe,
-                    Abi::Rust,
-                ));
-
-                return Some(ExpectedSig { cause_span, sig });
+                return_ty = Some(ret_projection.term.expect_type());
+                break;
             }
         }
 
-        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(
diff --git a/tests/ui/async-await/async-closures/sig-from-bare-fn.rs b/tests/ui/async-await/async-closures/sig-from-bare-fn.rs
new file mode 100644
index 00000000000..a679471a3b3
--- /dev/null
+++ b/tests/ui/async-await/async-closures/sig-from-bare-fn.rs
@@ -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() {}