about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2023-09-15 05:00:39 +0000
committerMichael Goulet <michael@errs.io>2023-09-21 19:18:14 +0000
commit9072415252d6dde7f23acc21a6781f27c703ac9d (patch)
tree2f462202bfe1f7f1d644e1292c5c4e49b2597e28
parent087a571e708560bad84b1dd8de107c8fdc2a3044 (diff)
downloadrust-9072415252d6dde7f23acc21a6781f27c703ac9d.tar.gz
rust-9072415252d6dde7f23acc21a6781f27c703ac9d.zip
Suggest desugaring to RPITIT when AFIT is required to be an auto trait
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs136
-rw-r--r--tests/ui/async-await/in-trait/auxiliary/foreign-async-fn.rs7
-rw-r--r--tests/ui/async-await/in-trait/missing-send-bound.stderr5
-rw-r--r--tests/ui/async-await/in-trait/send-on-async-fn-in-trait.fixed20
-rw-r--r--tests/ui/async-await/in-trait/send-on-async-fn-in-trait.rs20
-rw-r--r--tests/ui/async-await/in-trait/send-on-async-fn-in-trait.stderr43
-rw-r--r--tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.rs15
-rw-r--r--tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.stderr23
9 files changed, 270 insertions, 0 deletions
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index 7d754cbafa3..d3e0d3b8b18 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -987,6 +987,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
                         }
 
                         self.explain_hrtb_projection(&mut err, trait_predicate, obligation.param_env, &obligation.cause);
+                        self.suggest_desugaring_async_fn_in_trait(&mut err, trait_ref);
 
                         // Return early if the trait is Debug or Display and the invocation
                         // originates within a standard library macro, because the output
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index a08ebe5a9ea..17e0e4fcb48 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -414,6 +414,12 @@ pub trait TypeErrCtxtExt<'tcx> {
         param_env: ty::ParamEnv<'tcx>,
         cause: &ObligationCause<'tcx>,
     );
+
+    fn suggest_desugaring_async_fn_in_trait(
+        &self,
+        err: &mut Diagnostic,
+        trait_ref: ty::PolyTraitRef<'tcx>,
+    );
 }
 
 fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
@@ -4100,6 +4106,136 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
             });
         }
     }
+
+    fn suggest_desugaring_async_fn_in_trait(
+        &self,
+        err: &mut Diagnostic,
+        trait_ref: ty::PolyTraitRef<'tcx>,
+    ) {
+        // Don't suggest if RTN is active -- we should prefer a where-clause bound instead.
+        if self.tcx.features().return_type_notation {
+            return;
+        }
+
+        let trait_def_id = trait_ref.def_id();
+
+        // Only suggest specifying auto traits
+        if !self.tcx.trait_is_auto(trait_def_id) {
+            return;
+        }
+
+        // Look for an RPITIT
+        let ty::Alias(ty::Projection, alias_ty) = trait_ref.self_ty().skip_binder().kind() else {
+            return;
+        };
+        let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, opaque_def_id }) =
+            self.tcx.opt_rpitit_info(alias_ty.def_id)
+        else {
+            return;
+        };
+
+        let auto_trait = self.tcx.def_path_str(trait_def_id);
+        // ... which is a local function
+        let Some(fn_def_id) = fn_def_id.as_local() else {
+            // If it's not local, we can at least mention that the method is async, if it is.
+            if self.tcx.asyncness(fn_def_id).is_async() {
+                err.span_note(
+                    self.tcx.def_span(fn_def_id),
+                    format!(
+                        "`{}::{}` is an `async fn` in trait, which does not \
+                    automatically imply that its future is `{auto_trait}`",
+                        alias_ty.trait_ref(self.tcx),
+                        self.tcx.item_name(fn_def_id)
+                    ),
+                );
+            }
+            return;
+        };
+        let Some(hir::Node::TraitItem(item)) = self.tcx.hir().find_by_def_id(fn_def_id) else {
+            return;
+        };
+
+        // ... whose signature is `async` (i.e. this is an AFIT)
+        let (sig, body) = item.expect_fn();
+        let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
+            return;
+        };
+        let Ok(async_span) =
+            self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
+        else {
+            return;
+        };
+        let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
+            sig.decl.output
+        else {
+            // This should never happen, but let's not ICE.
+            return;
+        };
+
+        // Check that this is *not* a nested `impl Future` RPIT in an async fn
+        // (i.e. `async fn foo() -> impl Future`)
+        if def.owner_id.to_def_id() != opaque_def_id {
+            return;
+        }
+
+        let future = self.tcx.hir().item(*def).expect_opaque_ty();
+        let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
+            // `async fn` should always lower to a lang item bound... but don't ICE.
+            return;
+        };
+        let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
+            generics.bindings.get(0).map(|binding| binding.kind)
+        else {
+            // Also should never happen.
+            return;
+        };
+
+        let function_name = self.tcx.def_path_str(fn_def_id);
+
+        let mut sugg = if future_output_ty.span.is_empty() {
+            vec![
+                (async_span, String::new()),
+                (
+                    future_output_ty.span,
+                    format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
+                ),
+            ]
+        } else {
+            vec![
+                (
+                    future_output_ty.span.shrink_to_lo(),
+                    "impl std::future::Future<Output = ".to_owned(),
+                ),
+                (future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
+                (async_span, String::new()),
+            ]
+        };
+
+        // If there's a body, we also need to wrap it in `async {}`
+        if let hir::TraitFn::Provided(body) = body {
+            let body = self.tcx.hir().body(*body);
+            let body_span = body.value.span;
+            let body_span_without_braces =
+                body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
+            if body_span_without_braces.is_empty() {
+                sugg.push((body_span_without_braces, " async {} ".to_owned()));
+            } else {
+                sugg.extend([
+                    (body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
+                    (body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
+                ]);
+            }
+        }
+
+        err.multipart_suggestion(
+            format!(
+                "`{auto_trait}` can be made part of the associated future's \
+                guarantees for all implementations of `{function_name}`"
+            ),
+            sugg,
+            Applicability::MachineApplicable,
+        );
+    }
 }
 
 /// Add a hint to add a missing borrow or remove an unnecessary one.
diff --git a/tests/ui/async-await/in-trait/auxiliary/foreign-async-fn.rs b/tests/ui/async-await/in-trait/auxiliary/foreign-async-fn.rs
new file mode 100644
index 00000000000..bba886f175e
--- /dev/null
+++ b/tests/ui/async-await/in-trait/auxiliary/foreign-async-fn.rs
@@ -0,0 +1,7 @@
+// edition:2021
+
+#![feature(async_fn_in_trait)]
+
+pub trait Foo {
+    async fn test();
+}
diff --git a/tests/ui/async-await/in-trait/missing-send-bound.stderr b/tests/ui/async-await/in-trait/missing-send-bound.stderr
index 18185b75554..7e59d94d456 100644
--- a/tests/ui/async-await/in-trait/missing-send-bound.stderr
+++ b/tests/ui/async-await/in-trait/missing-send-bound.stderr
@@ -15,6 +15,11 @@ note: required by a bound in `assert_is_send`
    |
 LL | fn assert_is_send(_: impl Send) {}
    |                           ^^^^ required by this bound in `assert_is_send`
+help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::bar`
+   |
+LL -     async fn bar();
+LL +     fn bar() -> impl std::future::Future<Output = ()> + Send;
+   |
 
 error: aborting due to previous error
 
diff --git a/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.fixed b/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.fixed
new file mode 100644
index 00000000000..33c00587439
--- /dev/null
+++ b/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.fixed
@@ -0,0 +1,20 @@
+// run-rustfix
+// edition: 2021
+
+#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
+#![allow(unused)]
+
+trait Foo {
+    fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
+    fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
+}
+
+fn bar<T: Foo>() {
+    fn needs_send(_: impl Send) {}
+    needs_send(T::test());
+    //~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
+    needs_send(T::test2());
+    //~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
+}
+
+fn main() {}
diff --git a/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.rs b/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.rs
new file mode 100644
index 00000000000..96b623d6988
--- /dev/null
+++ b/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.rs
@@ -0,0 +1,20 @@
+// run-rustfix
+// edition: 2021
+
+#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
+#![allow(unused)]
+
+trait Foo {
+    async fn test() -> () {}
+    async fn test2() -> i32 { 1 + 2 }
+}
+
+fn bar<T: Foo>() {
+    fn needs_send(_: impl Send) {}
+    needs_send(T::test());
+    //~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
+    needs_send(T::test2());
+    //~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
+}
+
+fn main() {}
diff --git a/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.stderr b/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.stderr
new file mode 100644
index 00000000000..4319a14118b
--- /dev/null
+++ b/tests/ui/async-await/in-trait/send-on-async-fn-in-trait.stderr
@@ -0,0 +1,43 @@
+error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
+  --> $DIR/send-on-async-fn-in-trait.rs:14:16
+   |
+LL |     needs_send(T::test());
+   |     ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
+   |     |
+   |     required by a bound introduced by this call
+   |
+   = help: the trait `Send` is not implemented for `impl Future<Output = ()>`
+note: required by a bound in `needs_send`
+  --> $DIR/send-on-async-fn-in-trait.rs:13:27
+   |
+LL |     fn needs_send(_: impl Send) {}
+   |                           ^^^^ required by this bound in `needs_send`
+help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test`
+   |
+LL -     async fn test() -> () {}
+LL +     fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
+   |
+
+error[E0277]: `impl Future<Output = i32>` cannot be sent between threads safely
+  --> $DIR/send-on-async-fn-in-trait.rs:16:16
+   |
+LL |     needs_send(T::test2());
+   |     ---------- ^^^^^^^^^^ `impl Future<Output = i32>` cannot be sent between threads safely
+   |     |
+   |     required by a bound introduced by this call
+   |
+   = help: the trait `Send` is not implemented for `impl Future<Output = i32>`
+note: required by a bound in `needs_send`
+  --> $DIR/send-on-async-fn-in-trait.rs:13:27
+   |
+LL |     fn needs_send(_: impl Send) {}
+   |                           ^^^^ required by this bound in `needs_send`
+help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test2`
+   |
+LL -     async fn test2() -> i32 { 1 + 2 }
+LL +     fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
+   |
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0277`.
diff --git a/tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.rs b/tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.rs
new file mode 100644
index 00000000000..83b69d72a96
--- /dev/null
+++ b/tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.rs
@@ -0,0 +1,15 @@
+// aux-build:foreign-async-fn.rs
+// edition:2021
+
+#![feature(async_fn_in_trait)]
+
+extern crate foreign_async_fn;
+use foreign_async_fn::Foo;
+
+fn bar<T: Foo>() {
+    fn needs_send(_: impl Send) {}
+    needs_send(T::test());
+    //~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
+}
+
+fn main() {}
diff --git a/tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.stderr b/tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.stderr
new file mode 100644
index 00000000000..f337a04ba19
--- /dev/null
+++ b/tests/ui/async-await/in-trait/send-on-foreign-async-fn-in-trait.stderr
@@ -0,0 +1,23 @@
+error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
+  --> $DIR/send-on-foreign-async-fn-in-trait.rs:11:16
+   |
+LL |     needs_send(T::test());
+   |     ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
+   |     |
+   |     required by a bound introduced by this call
+   |
+   = help: the trait `Send` is not implemented for `impl Future<Output = ()>`
+note: `<T as Foo>::test` is an `async fn` in trait, which does not automatically imply that its future is `Send`
+  --> $DIR/auxiliary/foreign-async-fn.rs:6:5
+   |
+LL |     async fn test();
+   |     ^^^^^^^^^^^^^^^^
+note: required by a bound in `needs_send`
+  --> $DIR/send-on-foreign-async-fn-in-trait.rs:10:27
+   |
+LL |     fn needs_send(_: impl Send) {}
+   |                           ^^^^ required by this bound in `needs_send`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0277`.