Skip to content

Commit

Permalink
Rollup merge of rust-lang#114654 - estebank:suggest-pin-macro, r=davi…
Browse files Browse the repository at this point in the history
…dtwco

Suggest `pin!()` instead of `Pin::new()` when appropriate

When encountering a type that needs to be pinned but that is `!Unpin`, suggest using the `pin!()` macro.

Fix rust-lang#57994.
  • Loading branch information
matthiaskrgr authored Oct 3, 2023
2 parents e3c631b + 6b58fbf commit 535cd8d
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 22 deletions.
74 changes: 68 additions & 6 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2494,10 +2494,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Try alternative arbitrary self types that could fulfill this call.
// FIXME: probe for all types that *could* be arbitrary self-types, not
// just this list.
for (rcvr_ty, post) in &[
(rcvr_ty, ""),
(Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_erased, rcvr_ty), "&mut "),
(Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, rcvr_ty), "&"),
for (rcvr_ty, post, pin_call) in &[
(rcvr_ty, "", None),
(
Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_erased, rcvr_ty),
"&mut ",
Some("as_mut"),
),
(
Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, rcvr_ty),
"&",
Some("as_ref"),
),
] {
match self.lookup_probe_for_diagnostic(
item_name,
Expand Down Expand Up @@ -2531,6 +2539,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Err(_) => (),
}

let pred = ty::TraitRef::new(
self.tcx,
self.tcx.lang_items().unpin_trait().unwrap(),
[*rcvr_ty],
);
let unpin = self.predicate_must_hold_considering_regions(&Obligation::new(
self.tcx,
ObligationCause::misc(rcvr.span, self.body_id),
self.param_env,
pred,
));
for (rcvr_ty, pre) in &[
(Ty::new_lang_item(self.tcx, *rcvr_ty, LangItem::OwnedBox), "Box::new"),
(Ty::new_lang_item(self.tcx, *rcvr_ty, LangItem::Pin), "Pin::new"),
Expand All @@ -2554,7 +2573,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Explicitly ignore the `Pin::as_ref()` method as `Pin` does not
// implement the `AsRef` trait.
let skip = skippable.contains(&did)
|| (("Pin::new" == *pre) && (sym::as_ref == item_name.name))
|| (("Pin::new" == *pre) && ((sym::as_ref == item_name.name) || !unpin))
|| inputs_len.is_some_and(|inputs_len| pick.item.kind == ty::AssocKind::Fn && self.tcx.fn_sig(pick.item.def_id).skip_binder().skip_binder().inputs().len() != inputs_len);
// Make sure the method is defined for the *actual* receiver: we don't
// want to treat `Box<Self>` as a receiver if it only works because of
Expand All @@ -2566,7 +2585,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
err.multipart_suggestion(
"consider wrapping the receiver expression with the \
appropriate type",
appropriate type",
vec![
(rcvr.span.shrink_to_lo(), format!("{pre}({post}")),
(rcvr.span.shrink_to_hi(), ")".to_string()),
Expand All @@ -2578,6 +2597,49 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
}
// We special case the situation where `Pin::new` wouldn't work, and instead
// suggest using the `pin!()` macro instead.
if let Some(new_rcvr_t) = Ty::new_lang_item(self.tcx, *rcvr_ty, LangItem::Pin)
// We didn't find an alternative receiver for the method.
&& !alt_rcvr_sugg
// `T: !Unpin`
&& !unpin
// The method isn't `as_ref`, as it would provide a wrong suggestion for `Pin`.
&& sym::as_ref != item_name.name
// Either `Pin::as_ref` or `Pin::as_mut`.
&& let Some(pin_call) = pin_call
// Search for `item_name` as a method accessible on `Pin<T>`.
&& let Ok(pick) = self.lookup_probe_for_diagnostic(
item_name,
new_rcvr_t,
rcvr,
ProbeScope::AllTraits,
return_type,
)
// We skip some common traits that we don't want to consider because autoderefs
// would take care of them.
&& !skippable.contains(&Some(pick.item.container_id(self.tcx)))
// We don't want to go through derefs.
&& pick.autoderefs == 0
// Check that the method of the same name that was found on the new `Pin<T>`
// receiver has the same number of arguments that appear in the user's code.
&& inputs_len.is_some_and(|inputs_len| pick.item.kind == ty::AssocKind::Fn && self.tcx.fn_sig(pick.item.def_id).skip_binder().skip_binder().inputs().len() == inputs_len)
{
let indent = self.tcx.sess
.source_map()
.indentation_before(rcvr.span)
.unwrap_or_else(|| " ".to_string());
err.multipart_suggestion(
"consider pinning the expression",
vec![
(rcvr.span.shrink_to_lo(), format!("let mut pinned = std::pin::pin!(")),
(rcvr.span.shrink_to_hi(), format!(");\n{indent}pinned.{pin_call}()")),
],
Applicability::MaybeIncorrect,
);
// We don't care about the other suggestions.
alt_rcvr_sugg = true;
}
}
}
if self.suggest_valid_traits(err, valid_out_of_scope_traits) {
Expand Down
16 changes: 16 additions & 0 deletions tests/ui/async-await/issue-108572.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// edition: 2021
// run-rustfix
#![allow(unused_must_use, dead_code)]

use std::future::Future;
fn foo() -> impl Future<Output=()> {
async { }
}

fn bar(cx: &mut std::task::Context<'_>) {
let fut = foo();
let mut pinned = std::pin::pin!(fut);
pinned.as_mut().poll(cx);
//~^ ERROR no method named `poll` found for opaque type `impl Future<Output = ()>` in the current scope [E0599]
}
fn main() {}
7 changes: 5 additions & 2 deletions tests/ui/async-await/issue-108572.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// edition: 2021
// run-rustfix
#![allow(unused_must_use, dead_code)]

use std::future::Future;
fn foo() -> impl Future<Output=()> {
async { }
}

fn main() {
fn bar(cx: &mut std::task::Context<'_>) {
let fut = foo();
fut.poll();
fut.poll(cx);
//~^ ERROR no method named `poll` found for opaque type `impl Future<Output = ()>` in the current scope [E0599]
}
fn main() {}
9 changes: 7 additions & 2 deletions tests/ui/async-await/issue-108572.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
error[E0599]: no method named `poll` found for opaque type `impl Future<Output = ()>` in the current scope
--> $DIR/issue-108572.rs:10:9
--> $DIR/issue-108572.rs:12:9
|
LL | fut.poll();
LL | fut.poll(cx);
| ^^^^ method not found in `impl Future<Output = ()>`
|
= help: method `poll` found on `Pin<&mut impl Future<Output = ()>>`, see documentation for `std::pin::Pin`
= help: self type must be pinned to call `Future::poll`, see https://rust-lang.github.io/async-book/04_pinning/01_chapter.html#pinning-in-practice
help: consider pinning the expression
|
LL ~ let mut pinned = std::pin::pin!(fut);
LL ~ pinned.as_mut().poll(cx);
|

error: aborting due to previous error

Expand Down
8 changes: 3 additions & 5 deletions tests/ui/async-await/pin-needed-to-poll.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ LL | struct Sleep;
...
LL | self.sleep.poll(cx)
| ^^^^ method not found in `Sleep`
--> $SRC_DIR/core/src/future/future.rs:LL:COL
|
= note: the method is available for `Pin<&mut Sleep>` here
help: consider pinning the expression
|
help: consider wrapping the receiver expression with the appropriate type
LL ~ let mut pinned = std::pin::pin!(self.sleep);
LL ~ pinned.as_mut().poll(cx)
|
LL | Pin::new(&mut self.sleep).poll(cx)
| +++++++++++++ +

error: aborting due to previous error

Expand Down
3 changes: 2 additions & 1 deletion tests/ui/self/arbitrary_self_types_needing_mut_pin.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ impl S {
}

fn main() {
Pin::new(&mut S).x(); //~ ERROR no method named `x` found
let mut pinned = std::pin::pin!(S);
pinned.as_mut().x(); //~ ERROR no method named `x` found
}
10 changes: 4 additions & 6 deletions tests/ui/self/arbitrary_self_types_needing_mut_pin.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ error[E0599]: no method named `x` found for struct `S` in the current scope
LL | struct S;
| -------- method `x` not found for this struct
...
LL | fn x(self: Pin<&mut Self>) {
| - the method is available for `Pin<&mut S>` here
...
LL | S.x();
| ^ method not found in `S`
|
help: consider wrapping the receiver expression with the appropriate type
help: consider pinning the expression
|
LL ~ let mut pinned = std::pin::pin!(S);
LL ~ pinned.as_mut().x();
|
LL | Pin::new(&mut S).x();
| +++++++++++++ +

error: aborting due to previous error

Expand Down

0 comments on commit 535cd8d

Please sign in to comment.