Skip to content

Commit e9856d7

Browse files
committed
Do not suggest removing param & when expr also has &
1 parent bcded33 commit e9856d7

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
@@ -243,6 +243,15 @@ pub fn suggest_restriction<'tcx, G: EmissionGuarantee>(
243243
}
244244
}
245245

246+
/// A single layer of `&` peeled from an expression, used by
247+
/// [`TypeErrCtxt::peel_expr_refs`].
248+
struct PeeledRef<'tcx> {
249+
/// The span covering the `&` (and any whitespace/mutability keyword) to remove.
250+
span: Span,
251+
/// The type after peeling this layer (and all prior layers).
252+
peeled_ty: Ty<'tcx>,
253+
}
254+
246255
impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
247256
pub fn note_field_shadowed_by_private_candidate_in_cause(
248257
&self,
@@ -1818,6 +1827,85 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
18181827
);
18191828
}
18201829

1830+
/// Peel `&`-borrows from an expression, following through untyped let-bindings.
1831+
/// Returns a list of removable `&` layers (each with the span to remove and the
1832+
/// resulting type), plus an optional terminal [`hir::Param`] when the chain ends
1833+
/// at a function parameter (including async-fn desugared parameters).
1834+
fn peel_expr_refs(
1835+
&self,
1836+
mut expr: &'tcx hir::Expr<'tcx>,
1837+
mut ty: Ty<'tcx>,
1838+
) -> (Vec<PeeledRef<'tcx>>, Option<&'tcx hir::Param<'tcx>>) {
1839+
let mut refs = Vec::new();
1840+
'outer: loop {
1841+
while let hir::ExprKind::AddrOf(_, _, borrowed) = expr.kind {
1842+
let span =
1843+
if let Some(borrowed_span) = borrowed.span.find_ancestor_inside(expr.span) {
1844+
expr.span.until(borrowed_span)
1845+
} else {
1846+
break 'outer;
1847+
};
1848+
1849+
// Double check that the span actually corresponds to a borrow,
1850+
// rather than some macro garbage.
1851+
// The span may include leading parens from parenthesized expressions
1852+
// (e.g., `(&expr)` where HIR removes the Paren but keeps the span).
1853+
// In that case, trim the span to start at the `&`.
1854+
let span = match self.tcx.sess.source_map().span_to_snippet(span) {
1855+
Ok(ref snippet) if snippet.starts_with("&") => span,
1856+
Ok(ref snippet) if let Some(amp) = snippet.find('&') => {
1857+
span.with_lo(span.lo() + BytePos(amp as u32))
1858+
}
1859+
_ => break 'outer,
1860+
};
1861+
1862+
let ty::Ref(_, inner_ty, _) = ty.kind() else {
1863+
break 'outer;
1864+
};
1865+
ty = *inner_ty;
1866+
refs.push(PeeledRef { span, peeled_ty: ty });
1867+
expr = borrowed;
1868+
}
1869+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1870+
&& let Res::Local(hir_id) = path.res
1871+
&& let hir::Node::Pat(binding) = self.tcx.hir_node(hir_id)
1872+
{
1873+
match self.tcx.parent_hir_node(binding.hir_id) {
1874+
// Untyped let-binding: follow to its initializer.
1875+
hir::Node::LetStmt(local)
1876+
if local.ty.is_none()
1877+
&& let Some(init) = local.init =>
1878+
{
1879+
expr = init;
1880+
continue;
1881+
}
1882+
// Async fn desugared parameter: `let x = __arg0;` with AsyncFn source.
1883+
// Follow to the original parameter.
1884+
hir::Node::LetStmt(local)
1885+
if matches!(local.source, hir::LocalSource::AsyncFn)
1886+
&& let Some(init) = local.init
1887+
&& let hir::ExprKind::Path(hir::QPath::Resolved(None, arg_path)) =
1888+
init.kind
1889+
&& let Res::Local(arg_hir_id) = arg_path.res
1890+
&& let hir::Node::Pat(arg_binding) = self.tcx.hir_node(arg_hir_id)
1891+
&& let hir::Node::Param(param) =
1892+
self.tcx.parent_hir_node(arg_binding.hir_id) =>
1893+
{
1894+
return (refs, Some(param));
1895+
}
1896+
// Direct parameter reference.
1897+
hir::Node::Param(param) => {
1898+
return (refs, Some(param));
1899+
}
1900+
_ => break 'outer,
1901+
}
1902+
} else {
1903+
break 'outer;
1904+
}
1905+
}
1906+
(refs, None)
1907+
}
1908+
18211909
/// Whenever references are used by mistake, like `for (i, e) in &vec.iter().enumerate()`,
18221910
/// suggest removing these references until we reach a type that implements the trait.
18231911
pub(super) fn suggest_remove_reference(
@@ -1894,53 +1982,40 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
18941982
}
18951983

18961984
// Maybe suggest removal of borrows from expressions, like in `for i in &&&foo {}`.
1897-
let Some(mut expr) = expr_finder.result else {
1985+
let Some(expr) = expr_finder.result else {
18981986
return false;
18991987
};
1900-
let mut count = 0;
1901-
let mut suggestions = vec![];
19021988
// Skipping binder here, remapping below
1903-
let mut suggested_ty = trait_pred.self_ty().skip_binder();
1904-
'outer: loop {
1905-
while let hir::ExprKind::AddrOf(_, _, borrowed) = expr.kind {
1906-
count += 1;
1907-
let span =
1908-
if let Some(borrowed_span) = borrowed.span.find_ancestor_inside(expr.span) {
1909-
expr.span.until(borrowed_span)
1910-
} else {
1911-
break 'outer;
1912-
};
1913-
1914-
// Double check that the span we extracted actually corresponds to a borrow,
1915-
// rather than some macro garbage.
1916-
match self.tcx.sess.source_map().span_to_snippet(span) {
1917-
Ok(snippet) if snippet.starts_with("&") => {}
1918-
_ => break 'outer,
1919-
}
1920-
1921-
suggestions.push((span, String::new()));
1922-
1923-
let ty::Ref(_, inner_ty, _) = suggested_ty.kind() else {
1924-
break 'outer;
1925-
};
1926-
suggested_ty = *inner_ty;
1927-
1928-
expr = borrowed;
1989+
let suggested_ty = trait_pred.self_ty().skip_binder();
1990+
let (peeled_refs, _) = self.peel_expr_refs(expr, suggested_ty);
1991+
for (i, peeled) in peeled_refs.iter().enumerate() {
1992+
let suggestions: Vec<_> =
1993+
peeled_refs[..=i].iter().map(|r| (r.span, String::new())).collect();
1994+
if maybe_suggest(peeled.peeled_ty, i + 1, suggestions) {
1995+
return true;
1996+
}
1997+
}
1998+
false
1999+
}
19292000

1930-
if maybe_suggest(suggested_ty, count, suggestions.clone()) {
2001+
/// Suggest removing `&` from a function parameter type like `&impl Future`.
2002+
fn suggest_remove_ref_from_param(&self, param: &hir::Param<'_>, err: &mut Diag<'_>) -> bool {
2003+
if let Some(decl) = self.tcx.parent_hir_node(param.hir_id).fn_decl()
2004+
&& let Some(input_ty) = decl.inputs.iter().find(|t| param.ty_span.contains(t.span))
2005+
&& let hir::TyKind::Ref(_, mut_ty) = input_ty.kind
2006+
{
2007+
let ref_span = input_ty.span.until(mut_ty.ty.span);
2008+
match self.tcx.sess.source_map().span_to_snippet(ref_span) {
2009+
Ok(snippet) if snippet.starts_with("&") => {
2010+
err.span_suggestion_verbose(
2011+
ref_span,
2012+
"consider removing the `&` from the parameter type",
2013+
"",
2014+
Applicability::MaybeIncorrect,
2015+
);
19312016
return true;
19322017
}
1933-
}
1934-
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1935-
&& let Res::Local(hir_id) = path.res
1936-
&& let hir::Node::Pat(binding) = self.tcx.hir_node(hir_id)
1937-
&& let hir::Node::LetStmt(local) = self.tcx.parent_hir_node(binding.hir_id)
1938-
&& let None = local.ty
1939-
&& let Some(binding_expr) = local.init
1940-
{
1941-
expr = binding_expr;
1942-
} else {
1943-
break 'outer;
2018+
_ => {}
19442019
}
19452020
}
19462021
false
@@ -1959,6 +2034,89 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
19592034
// could also check if it is an fn call (very likely) and suggest changing *that*, if
19602035
// it is from the local crate.
19612036

2037+
// If the type is `&..&T` where `T: Future`, suggest removing `&`
2038+
// instead of removing `.await`.
2039+
if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) =
2040+
obligation.predicate.kind().skip_binder()
2041+
{
2042+
let self_ty = pred.self_ty();
2043+
let future_trait =
2044+
self.tcx.require_lang_item(LangItem::Future, obligation.cause.span);
2045+
2046+
// Peel through references to check if there's a Future underneath.
2047+
let has_future = {
2048+
let mut ty = self_ty;
2049+
loop {
2050+
match *ty.kind() {
2051+
ty::Ref(_, inner_ty, _)
2052+
if !matches!(inner_ty.kind(), ty::Dynamic(..)) =>
2053+
{
2054+
if self
2055+
.type_implements_trait(
2056+
future_trait,
2057+
[inner_ty],
2058+
obligation.param_env,
2059+
)
2060+
.must_apply_modulo_regions()
2061+
{
2062+
break true;
2063+
}
2064+
ty = inner_ty;
2065+
}
2066+
_ => break false,
2067+
}
2068+
}
2069+
};
2070+
2071+
if has_future {
2072+
let (peeled_refs, terminal_param) = self.peel_expr_refs(expr, self_ty);
2073+
2074+
// Try removing `&`s from the expression.
2075+
for (i, peeled) in peeled_refs.iter().enumerate() {
2076+
if self
2077+
.type_implements_trait(
2078+
future_trait,
2079+
[peeled.peeled_ty],
2080+
obligation.param_env,
2081+
)
2082+
.must_apply_modulo_regions()
2083+
{
2084+
let count = i + 1;
2085+
let msg = if count == 1 {
2086+
"consider removing the leading `&`-reference".to_string()
2087+
} else {
2088+
format!("consider removing {count} leading `&`-references")
2089+
};
2090+
let suggestions: Vec<_> =
2091+
peeled_refs[..=i].iter().map(|r| (r.span, String::new())).collect();
2092+
err.multipart_suggestion(
2093+
msg,
2094+
suggestions,
2095+
Applicability::MachineApplicable,
2096+
);
2097+
return;
2098+
}
2099+
}
2100+
2101+
// Try removing `&` from the parameter type, but only when there's
2102+
// no `&` in the expression itself (otherwise removing from the param
2103+
// alone wouldn't fix the error).
2104+
if peeled_refs.is_empty()
2105+
&& let Some(param) = terminal_param
2106+
&& self.suggest_remove_ref_from_param(param, err)
2107+
{
2108+
return;
2109+
}
2110+
2111+
// Fallback: emit a help message when we can't provide a specific span.
2112+
err.help(
2113+
"a reference to a future is not a future; \
2114+
consider removing the leading `&`-reference",
2115+
);
2116+
return;
2117+
}
2118+
}
2119+
19622120
// use nth(1) to skip one layer of desugaring from `IntoIter::into_iter`
19632121
if let Some((_, hir::Node::Expr(await_expr))) = self.tcx.hir_parent_iter(*hir_id).nth(1)
19642122
&& 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)