Skip to content

Commit 03dc6a3

Browse files
authored
Rollup merge of #154933 - fru1tworld:fix-87211-ref-future-diagnostic, r=chenyukang
Suggest removing `&` when awaiting a reference to a future Fixes #87211 When `.await`ing `&impl Future`, suggest removing the `&` instead of removing `.await`.
2 parents a8e1664 + e9856d7 commit 03dc6a3

3 files changed

Lines changed: 361 additions & 41 deletions

File tree

compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs

Lines changed: 199 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,15 @@ pub fn suggest_restriction<'tcx, G: EmissionGuarantee>(
246246
}
247247
}
248248

249+
/// A single layer of `&` peeled from an expression, used by
250+
/// [`TypeErrCtxt::peel_expr_refs`].
251+
struct PeeledRef<'tcx> {
252+
/// The span covering the `&` (and any whitespace/mutability keyword) to remove.
253+
span: Span,
254+
/// The type after peeling this layer (and all prior layers).
255+
peeled_ty: Ty<'tcx>,
256+
}
257+
249258
impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
250259
pub fn note_field_shadowed_by_private_candidate_in_cause(
251260
&self,
@@ -1882,6 +1891,85 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
18821891
);
18831892
}
18841893

1894+
/// Peel `&`-borrows from an expression, following through untyped let-bindings.
1895+
/// Returns a list of removable `&` layers (each with the span to remove and the
1896+
/// resulting type), plus an optional terminal [`hir::Param`] when the chain ends
1897+
/// at a function parameter (including async-fn desugared parameters).
1898+
fn peel_expr_refs(
1899+
&self,
1900+
mut expr: &'tcx hir::Expr<'tcx>,
1901+
mut ty: Ty<'tcx>,
1902+
) -> (Vec<PeeledRef<'tcx>>, Option<&'tcx hir::Param<'tcx>>) {
1903+
let mut refs = Vec::new();
1904+
'outer: loop {
1905+
while let hir::ExprKind::AddrOf(_, _, borrowed) = expr.kind {
1906+
let span =
1907+
if let Some(borrowed_span) = borrowed.span.find_ancestor_inside(expr.span) {
1908+
expr.span.until(borrowed_span)
1909+
} else {
1910+
break 'outer;
1911+
};
1912+
1913+
// Double check that the span actually corresponds to a borrow,
1914+
// rather than some macro garbage.
1915+
// The span may include leading parens from parenthesized expressions
1916+
// (e.g., `(&expr)` where HIR removes the Paren but keeps the span).
1917+
// In that case, trim the span to start at the `&`.
1918+
let span = match self.tcx.sess.source_map().span_to_snippet(span) {
1919+
Ok(ref snippet) if snippet.starts_with("&") => span,
1920+
Ok(ref snippet) if let Some(amp) = snippet.find('&') => {
1921+
span.with_lo(span.lo() + BytePos(amp as u32))
1922+
}
1923+
_ => break 'outer,
1924+
};
1925+
1926+
let ty::Ref(_, inner_ty, _) = ty.kind() else {
1927+
break 'outer;
1928+
};
1929+
ty = *inner_ty;
1930+
refs.push(PeeledRef { span, peeled_ty: ty });
1931+
expr = borrowed;
1932+
}
1933+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1934+
&& let Res::Local(hir_id) = path.res
1935+
&& let hir::Node::Pat(binding) = self.tcx.hir_node(hir_id)
1936+
{
1937+
match self.tcx.parent_hir_node(binding.hir_id) {
1938+
// Untyped let-binding: follow to its initializer.
1939+
hir::Node::LetStmt(local)
1940+
if local.ty.is_none()
1941+
&& let Some(init) = local.init =>
1942+
{
1943+
expr = init;
1944+
continue;
1945+
}
1946+
// Async fn desugared parameter: `let x = __arg0;` with AsyncFn source.
1947+
// Follow to the original parameter.
1948+
hir::Node::LetStmt(local)
1949+
if matches!(local.source, hir::LocalSource::AsyncFn)
1950+
&& let Some(init) = local.init
1951+
&& let hir::ExprKind::Path(hir::QPath::Resolved(None, arg_path)) =
1952+
init.kind
1953+
&& let Res::Local(arg_hir_id) = arg_path.res
1954+
&& let hir::Node::Pat(arg_binding) = self.tcx.hir_node(arg_hir_id)
1955+
&& let hir::Node::Param(param) =
1956+
self.tcx.parent_hir_node(arg_binding.hir_id) =>
1957+
{
1958+
return (refs, Some(param));
1959+
}
1960+
// Direct parameter reference.
1961+
hir::Node::Param(param) => {
1962+
return (refs, Some(param));
1963+
}
1964+
_ => break 'outer,
1965+
}
1966+
} else {
1967+
break 'outer;
1968+
}
1969+
}
1970+
(refs, None)
1971+
}
1972+
18851973
/// Whenever references are used by mistake, like `for (i, e) in &vec.iter().enumerate()`,
18861974
/// suggest removing these references until we reach a type that implements the trait.
18871975
pub(super) fn suggest_remove_reference(
@@ -1958,53 +2046,40 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
19582046
}
19592047

19602048
// Maybe suggest removal of borrows from expressions, like in `for i in &&&foo {}`.
1961-
let Some(mut expr) = expr_finder.result else {
2049+
let Some(expr) = expr_finder.result else {
19622050
return false;
19632051
};
1964-
let mut count = 0;
1965-
let mut suggestions = vec![];
19662052
// Skipping binder here, remapping below
1967-
let mut suggested_ty = trait_pred.self_ty().skip_binder();
1968-
'outer: loop {
1969-
while let hir::ExprKind::AddrOf(_, _, borrowed) = expr.kind {
1970-
count += 1;
1971-
let span =
1972-
if let Some(borrowed_span) = borrowed.span.find_ancestor_inside(expr.span) {
1973-
expr.span.until(borrowed_span)
1974-
} else {
1975-
break 'outer;
1976-
};
1977-
1978-
// Double check that the span we extracted actually corresponds to a borrow,
1979-
// rather than some macro garbage.
1980-
match self.tcx.sess.source_map().span_to_snippet(span) {
1981-
Ok(snippet) if snippet.starts_with("&") => {}
1982-
_ => break 'outer,
1983-
}
1984-
1985-
suggestions.push((span, String::new()));
1986-
1987-
let ty::Ref(_, inner_ty, _) = suggested_ty.kind() else {
1988-
break 'outer;
1989-
};
1990-
suggested_ty = *inner_ty;
1991-
1992-
expr = borrowed;
2053+
let suggested_ty = trait_pred.self_ty().skip_binder();
2054+
let (peeled_refs, _) = self.peel_expr_refs(expr, suggested_ty);
2055+
for (i, peeled) in peeled_refs.iter().enumerate() {
2056+
let suggestions: Vec<_> =
2057+
peeled_refs[..=i].iter().map(|r| (r.span, String::new())).collect();
2058+
if maybe_suggest(peeled.peeled_ty, i + 1, suggestions) {
2059+
return true;
2060+
}
2061+
}
2062+
false
2063+
}
19932064

1994-
if maybe_suggest(suggested_ty, count, suggestions.clone()) {
2065+
/// Suggest removing `&` from a function parameter type like `&impl Future`.
2066+
fn suggest_remove_ref_from_param(&self, param: &hir::Param<'_>, err: &mut Diag<'_>) -> bool {
2067+
if let Some(decl) = self.tcx.parent_hir_node(param.hir_id).fn_decl()
2068+
&& let Some(input_ty) = decl.inputs.iter().find(|t| param.ty_span.contains(t.span))
2069+
&& let hir::TyKind::Ref(_, mut_ty) = input_ty.kind
2070+
{
2071+
let ref_span = input_ty.span.until(mut_ty.ty.span);
2072+
match self.tcx.sess.source_map().span_to_snippet(ref_span) {
2073+
Ok(snippet) if snippet.starts_with("&") => {
2074+
err.span_suggestion_verbose(
2075+
ref_span,
2076+
"consider removing the `&` from the parameter type",
2077+
"",
2078+
Applicability::MaybeIncorrect,
2079+
);
19952080
return true;
19962081
}
1997-
}
1998-
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1999-
&& let Res::Local(hir_id) = path.res
2000-
&& let hir::Node::Pat(binding) = self.tcx.hir_node(hir_id)
2001-
&& let hir::Node::LetStmt(local) = self.tcx.parent_hir_node(binding.hir_id)
2002-
&& let None = local.ty
2003-
&& let Some(binding_expr) = local.init
2004-
{
2005-
expr = binding_expr;
2006-
} else {
2007-
break 'outer;
2082+
_ => {}
20082083
}
20092084
}
20102085
false
@@ -2023,6 +2098,89 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
20232098
// could also check if it is an fn call (very likely) and suggest changing *that*, if
20242099
// it is from the local crate.
20252100

2101+
// If the type is `&..&T` where `T: Future`, suggest removing `&`
2102+
// instead of removing `.await`.
2103+
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) =
2104+
obligation.predicate.kind().skip_binder()
2105+
{
2106+
let self_ty = pred.self_ty();
2107+
let future_trait =
2108+
self.tcx.require_lang_item(LangItem::Future, obligation.cause.span);
2109+
2110+
// Peel through references to check if there's a Future underneath.
2111+
let has_future = {
2112+
let mut ty = self_ty;
2113+
loop {
2114+
match *ty.kind() {
2115+
ty::Ref(_, inner_ty, _)
2116+
if !matches!(inner_ty.kind(), ty::Dynamic(..)) =>
2117+
{
2118+
if self
2119+
.type_implements_trait(
2120+
future_trait,
2121+
[inner_ty],
2122+
obligation.param_env,
2123+
)
2124+
.must_apply_modulo_regions()
2125+
{
2126+
break true;
2127+
}
2128+
ty = inner_ty;
2129+
}
2130+
_ => break false,
2131+
}
2132+
}
2133+
};
2134+
2135+
if has_future {
2136+
let (peeled_refs, terminal_param) = self.peel_expr_refs(expr, self_ty);
2137+
2138+
// Try removing `&`s from the expression.
2139+
for (i, peeled) in peeled_refs.iter().enumerate() {
2140+
if self
2141+
.type_implements_trait(
2142+
future_trait,
2143+
[peeled.peeled_ty],
2144+
obligation.param_env,
2145+
)
2146+
.must_apply_modulo_regions()
2147+
{
2148+
let count = i + 1;
2149+
let msg = if count == 1 {
2150+
"consider removing the leading `&`-reference".to_string()
2151+
} else {
2152+
format!("consider removing {count} leading `&`-references")
2153+
};
2154+
let suggestions: Vec<_> =
2155+
peeled_refs[..=i].iter().map(|r| (r.span, String::new())).collect();
2156+
err.multipart_suggestion(
2157+
msg,
2158+
suggestions,
2159+
Applicability::MachineApplicable,
2160+
);
2161+
return;
2162+
}
2163+
}
2164+
2165+
// Try removing `&` from the parameter type, but only when there's
2166+
// no `&` in the expression itself (otherwise removing from the param
2167+
// alone wouldn't fix the error).
2168+
if peeled_refs.is_empty()
2169+
&& let Some(param) = terminal_param
2170+
&& self.suggest_remove_ref_from_param(param, err)
2171+
{
2172+
return;
2173+
}
2174+
2175+
// Fallback: emit a help message when we can't provide a specific span.
2176+
err.help(
2177+
"a reference to a future is not a future; \
2178+
consider removing the leading `&`-reference",
2179+
);
2180+
return;
2181+
}
2182+
}
2183+
20262184
// use nth(1) to skip one layer of desugaring from `IntoIter::into_iter`
20272185
if let Some((_, hir::Node::Expr(await_expr))) = self.tcx.hir_parent_iter(*hir_id).nth(1)
20282186
&& let Some(expr_span) = expr.span.find_ancestor_inside_same_ctxt(await_expr.span)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//@ edition:2021
2+
// Regression test for #87211.
3+
// Test that we suggest removing `&` from references to futures,
4+
// including let-bindings and parameter types, but not `&dyn Future`.
5+
6+
async fn my_async_fn() {}
7+
8+
async fn foo() {
9+
let fut = &my_async_fn();
10+
fut.await; //~ ERROR `&impl Future<Output = ()>` is not a future
11+
}
12+
13+
async fn direct_ref_await() {
14+
(&my_async_fn()).await; //~ ERROR `&impl Future<Output = ()>` is not a future
15+
}
16+
17+
async fn bar(fut: &impl std::future::Future<Output = ()>) {
18+
fut.await; //~ ERROR is not a future
19+
}
20+
21+
async fn dyn_ref_param(fut: &dyn std::future::Future<Output = ()>) {
22+
fut.await; //~ ERROR is not a future
23+
}
24+
25+
async fn typed_let_binding() {
26+
let fut: &_ = &my_async_fn();
27+
fut.await; //~ ERROR `&impl Future<Output = ()>` is not a future
28+
}
29+
30+
async fn ref_param_borrowed_expr(fut: &impl std::future::Future<Output = ()>) {
31+
(&fut).await; //~ ERROR is not a future
32+
}
33+
34+
async fn double_ref_direct() {
35+
(&&my_async_fn()).await; //~ ERROR is not a future
36+
}
37+
38+
async fn double_ref_let() {
39+
let fut = &&my_async_fn();
40+
fut.await; //~ ERROR is not a future
41+
}
42+
43+
fn main() {}

0 commit comments

Comments
 (0)