Rollup merge of #119978 - compiler-errors:async-closure-captures, r=oli-obk
Move async closure parameters into the resultant closure's future eagerly Move async closure parameters into the closure's resultant future eagerly. Before, we used to desugar `async |p1, p2, ..| { body }` as `|p1, p2, ..| { || async { body } }`. Now, we desugar the above like `|p1, p2, ..| { async move { let p1 = p1; let p2 = p2; ... body } }`. This mirrors the same desugaring that `async fn` does with its parameter types, and the compiler literally uses the same code via a shared helper function. This removes the necessity for E0708, since now expressions like `async |x: i32| { x }` will not give you confusing borrow errors. This does *not* fix the case where async closures have self-borrows. This will come with a general implementation of async closures, which is still in the works. r? oli-obk
This commit is contained in:
commit
536fc22917
@ -14,10 +14,6 @@ ast_lowering_assoc_ty_parentheses =
|
||||
ast_lowering_async_coroutines_not_supported =
|
||||
`async` coroutines are not yet supported
|
||||
|
||||
ast_lowering_async_non_move_closure_not_supported =
|
||||
`async` non-`move` closures with parameters are not currently supported
|
||||
.help = consider using `let` statements to manually capture variables by reference before entering an `async move` closure
|
||||
|
||||
ast_lowering_att_syntax_only_x86 =
|
||||
the `att_syntax` option is only supported on x86
|
||||
|
||||
|
@ -145,14 +145,6 @@ pub struct ClosureCannotBeStatic {
|
||||
pub fn_decl_span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic, Clone, Copy)]
|
||||
#[help]
|
||||
#[diag(ast_lowering_async_non_move_closure_not_supported, code = "E0708")]
|
||||
pub struct AsyncNonMoveClosureNotSupported {
|
||||
#[primary_span]
|
||||
pub fn_decl_span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic, Clone, Copy)]
|
||||
#[diag(ast_lowering_functional_record_update_destructuring_assignment)]
|
||||
pub struct FunctionalRecordUpdateDestructuringAssignment {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::errors::{
|
||||
AsyncCoroutinesNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks,
|
||||
BaseExpressionDoubleDot, ClosureCannotBeStatic, CoroutineTooManyParameters,
|
||||
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot,
|
||||
ClosureCannotBeStatic, CoroutineTooManyParameters,
|
||||
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody,
|
||||
NeverPatternWithBody, NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure,
|
||||
UnderscoreExprLhsAssign,
|
||||
@ -13,7 +13,6 @@ use rustc_ast::*;
|
||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_middle::span_bug;
|
||||
use rustc_session::errors::report_lit_error;
|
||||
use rustc_span::source_map::{respan, Spanned};
|
||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||
@ -1028,28 +1027,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||
fn_decl_span: Span,
|
||||
fn_arg_span: Span,
|
||||
) -> hir::ExprKind<'hir> {
|
||||
let CoroutineKind::Async { closure_id: inner_closure_id, .. } = coroutine_kind else {
|
||||
span_bug!(fn_decl_span, "`async gen` and `gen` closures are not supported, yet");
|
||||
};
|
||||
|
||||
if let &ClosureBinder::For { span, .. } = binder {
|
||||
self.dcx().emit_err(NotSupportedForLifetimeBinderAsyncClosure { span });
|
||||
}
|
||||
|
||||
let (binder_clause, generic_params) = self.lower_closure_binder(binder);
|
||||
|
||||
let outer_decl =
|
||||
FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
|
||||
|
||||
let body = self.with_new_scopes(fn_decl_span, |this| {
|
||||
// FIXME(cramertj): allow `async` non-`move` closures with arguments.
|
||||
if capture_clause == CaptureBy::Ref && !decl.inputs.is_empty() {
|
||||
this.dcx().emit_err(AsyncNonMoveClosureNotSupported { fn_decl_span });
|
||||
}
|
||||
|
||||
// Transform `async |x: u8| -> X { ... }` into
|
||||
// `|x: u8| || -> X { ... }`.
|
||||
let body_id = this.lower_fn_body(&outer_decl, |this| {
|
||||
let body_id = this.lower_body(|this| {
|
||||
let async_ret_ty = if let FnRetTy::Ty(ty) = &decl.output {
|
||||
let itctx = ImplTraitContext::Disallowed(ImplTraitPosition::AsyncBlock);
|
||||
Some(hir::FnRetTy::Return(this.lower_ty(ty, &itctx)))
|
||||
@ -1057,22 +1044,26 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||
None
|
||||
};
|
||||
|
||||
let async_body = this.make_desugared_coroutine_expr(
|
||||
capture_clause,
|
||||
inner_closure_id,
|
||||
async_ret_ty,
|
||||
body.span,
|
||||
hir::CoroutineDesugaring::Async,
|
||||
hir::CoroutineSource::Closure,
|
||||
let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments(
|
||||
decl,
|
||||
|this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),
|
||||
body.span,
|
||||
coroutine_kind,
|
||||
hir::CoroutineSource::Closure,
|
||||
async_ret_ty,
|
||||
);
|
||||
let hir_id = this.lower_node_id(inner_closure_id);
|
||||
|
||||
let hir_id = this.lower_node_id(coroutine_kind.closure_id());
|
||||
this.maybe_forward_track_caller(body.span, closure_hir_id, hir_id);
|
||||
hir::Expr { hir_id, kind: async_body, span: this.lower_span(body.span) }
|
||||
|
||||
(parameters, expr)
|
||||
});
|
||||
body_id
|
||||
});
|
||||
|
||||
let outer_decl =
|
||||
FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
|
||||
|
||||
let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
|
||||
// We need to lower the declaration outside the new scope, because we
|
||||
// have to conserve the state of being inside a loop condition for the
|
||||
|
@ -1082,196 +1082,226 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||
let (Some(coroutine_kind), Some(body)) = (coroutine_kind, body) else {
|
||||
return self.lower_fn_body_block(span, decl, body);
|
||||
};
|
||||
let closure_id = coroutine_kind.closure_id();
|
||||
|
||||
self.lower_body(|this| {
|
||||
let mut parameters: Vec<hir::Param<'_>> = Vec::new();
|
||||
let mut statements: Vec<hir::Stmt<'_>> = Vec::new();
|
||||
|
||||
// Async function parameters are lowered into the closure body so that they are
|
||||
// captured and so that the drop order matches the equivalent non-async functions.
|
||||
//
|
||||
// from:
|
||||
//
|
||||
// async fn foo(<pattern>: <ty>, <pattern>: <ty>, <pattern>: <ty>) {
|
||||
// <body>
|
||||
// }
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// fn foo(__arg0: <ty>, __arg1: <ty>, __arg2: <ty>) {
|
||||
// async move {
|
||||
// let __arg2 = __arg2;
|
||||
// let <pattern> = __arg2;
|
||||
// let __arg1 = __arg1;
|
||||
// let <pattern> = __arg1;
|
||||
// let __arg0 = __arg0;
|
||||
// let <pattern> = __arg0;
|
||||
// drop-temps { <body> } // see comments later in fn for details
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If `<pattern>` is a simple ident, then it is lowered to a single
|
||||
// `let <pattern> = <pattern>;` statement as an optimization.
|
||||
//
|
||||
// Note that the body is embedded in `drop-temps`; an
|
||||
// equivalent desugaring would be `return { <body>
|
||||
// };`. The key point is that we wish to drop all the
|
||||
// let-bound variables and temporaries created in the body
|
||||
// (and its tail expression!) before we drop the
|
||||
// parameters (c.f. rust-lang/rust#64512).
|
||||
for (index, parameter) in decl.inputs.iter().enumerate() {
|
||||
let parameter = this.lower_param(parameter);
|
||||
let span = parameter.pat.span;
|
||||
|
||||
// Check if this is a binding pattern, if so, we can optimize and avoid adding a
|
||||
// `let <pat> = __argN;` statement. In this case, we do not rename the parameter.
|
||||
let (ident, is_simple_parameter) = match parameter.pat.kind {
|
||||
hir::PatKind::Binding(hir::BindingAnnotation(ByRef::No, _), _, ident, _) => {
|
||||
(ident, true)
|
||||
}
|
||||
// For `ref mut` or wildcard arguments, we can't reuse the binding, but
|
||||
// we can keep the same name for the parameter.
|
||||
// This lets rustdoc render it correctly in documentation.
|
||||
hir::PatKind::Binding(_, _, ident, _) => (ident, false),
|
||||
hir::PatKind::Wild => {
|
||||
(Ident::with_dummy_span(rustc_span::symbol::kw::Underscore), false)
|
||||
}
|
||||
_ => {
|
||||
// Replace the ident for bindings that aren't simple.
|
||||
let name = format!("__arg{index}");
|
||||
let ident = Ident::from_str(&name);
|
||||
|
||||
(ident, false)
|
||||
}
|
||||
};
|
||||
|
||||
let desugared_span = this.mark_span_with_reason(DesugaringKind::Async, span, None);
|
||||
|
||||
// Construct a parameter representing `__argN: <ty>` to replace the parameter of the
|
||||
// async function.
|
||||
//
|
||||
// If this is the simple case, this parameter will end up being the same as the
|
||||
// original parameter, but with a different pattern id.
|
||||
let stmt_attrs = this.attrs.get(¶meter.hir_id.local_id).copied();
|
||||
let (new_parameter_pat, new_parameter_id) = this.pat_ident(desugared_span, ident);
|
||||
let new_parameter = hir::Param {
|
||||
hir_id: parameter.hir_id,
|
||||
pat: new_parameter_pat,
|
||||
ty_span: this.lower_span(parameter.ty_span),
|
||||
span: this.lower_span(parameter.span),
|
||||
};
|
||||
|
||||
if is_simple_parameter {
|
||||
// If this is the simple case, then we only insert one statement that is
|
||||
// `let <pat> = <pat>;`. We re-use the original argument's pattern so that
|
||||
// `HirId`s are densely assigned.
|
||||
let expr = this.expr_ident(desugared_span, ident, new_parameter_id);
|
||||
let stmt = this.stmt_let_pat(
|
||||
stmt_attrs,
|
||||
desugared_span,
|
||||
Some(expr),
|
||||
parameter.pat,
|
||||
hir::LocalSource::AsyncFn,
|
||||
);
|
||||
statements.push(stmt);
|
||||
} else {
|
||||
// If this is not the simple case, then we construct two statements:
|
||||
//
|
||||
// ```
|
||||
// let __argN = __argN;
|
||||
// let <pat> = __argN;
|
||||
// ```
|
||||
//
|
||||
// The first statement moves the parameter into the closure and thus ensures
|
||||
// that the drop order is correct.
|
||||
//
|
||||
// The second statement creates the bindings that the user wrote.
|
||||
|
||||
// Construct the `let mut __argN = __argN;` statement. It must be a mut binding
|
||||
// because the user may have specified a `ref mut` binding in the next
|
||||
// statement.
|
||||
let (move_pat, move_id) = this.pat_ident_binding_mode(
|
||||
desugared_span,
|
||||
ident,
|
||||
hir::BindingAnnotation::MUT,
|
||||
);
|
||||
let move_expr = this.expr_ident(desugared_span, ident, new_parameter_id);
|
||||
let move_stmt = this.stmt_let_pat(
|
||||
None,
|
||||
desugared_span,
|
||||
Some(move_expr),
|
||||
move_pat,
|
||||
hir::LocalSource::AsyncFn,
|
||||
);
|
||||
|
||||
// Construct the `let <pat> = __argN;` statement. We re-use the original
|
||||
// parameter's pattern so that `HirId`s are densely assigned.
|
||||
let pattern_expr = this.expr_ident(desugared_span, ident, move_id);
|
||||
let pattern_stmt = this.stmt_let_pat(
|
||||
stmt_attrs,
|
||||
desugared_span,
|
||||
Some(pattern_expr),
|
||||
parameter.pat,
|
||||
hir::LocalSource::AsyncFn,
|
||||
);
|
||||
|
||||
statements.push(move_stmt);
|
||||
statements.push(pattern_stmt);
|
||||
};
|
||||
|
||||
parameters.push(new_parameter);
|
||||
}
|
||||
|
||||
let mkbody = |this: &mut LoweringContext<'_, 'hir>| {
|
||||
// Create a block from the user's function body:
|
||||
let user_body = this.lower_block_expr(body);
|
||||
|
||||
// Transform into `drop-temps { <user-body> }`, an expression:
|
||||
let desugared_span =
|
||||
this.mark_span_with_reason(DesugaringKind::Async, user_body.span, None);
|
||||
let user_body = this.expr_drop_temps(desugared_span, this.arena.alloc(user_body));
|
||||
|
||||
// As noted above, create the final block like
|
||||
//
|
||||
// ```
|
||||
// {
|
||||
// let $param_pattern = $raw_param;
|
||||
// ...
|
||||
// drop-temps { <user-body> }
|
||||
// }
|
||||
// ```
|
||||
let body = this.block_all(
|
||||
desugared_span,
|
||||
this.arena.alloc_from_iter(statements),
|
||||
Some(user_body),
|
||||
);
|
||||
|
||||
this.expr_block(body)
|
||||
};
|
||||
let desugaring_kind = match coroutine_kind {
|
||||
CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async,
|
||||
CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen,
|
||||
CoroutineKind::AsyncGen { .. } => hir::CoroutineDesugaring::AsyncGen,
|
||||
};
|
||||
let coroutine_expr = this.make_desugared_coroutine_expr(
|
||||
CaptureBy::Value { move_kw: rustc_span::DUMMY_SP },
|
||||
closure_id,
|
||||
None,
|
||||
let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments(
|
||||
decl,
|
||||
|this| this.lower_block_expr(body),
|
||||
body.span,
|
||||
desugaring_kind,
|
||||
coroutine_kind,
|
||||
hir::CoroutineSource::Fn,
|
||||
mkbody,
|
||||
None,
|
||||
);
|
||||
|
||||
let hir_id = this.lower_node_id(closure_id);
|
||||
// FIXME(async_fn_track_caller): Can this be moved above?
|
||||
let hir_id = this.lower_node_id(coroutine_kind.closure_id());
|
||||
this.maybe_forward_track_caller(body.span, fn_id, hir_id);
|
||||
let expr = hir::Expr { hir_id, kind: coroutine_expr, span: this.lower_span(body.span) };
|
||||
|
||||
(this.arena.alloc_from_iter(parameters), expr)
|
||||
(parameters, expr)
|
||||
})
|
||||
}
|
||||
|
||||
/// Lowers a desugared coroutine body after moving all of the arguments
|
||||
/// into the body. This is to make sure that the future actually owns the
|
||||
/// arguments that are passed to the function, and to ensure things like
|
||||
/// drop order are stable.
|
||||
pub fn lower_coroutine_body_with_moved_arguments(
|
||||
&mut self,
|
||||
decl: &FnDecl,
|
||||
lower_body: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::Expr<'hir>,
|
||||
body_span: Span,
|
||||
coroutine_kind: CoroutineKind,
|
||||
coroutine_source: hir::CoroutineSource,
|
||||
return_type_hint: Option<hir::FnRetTy<'hir>>,
|
||||
) -> (&'hir [hir::Param<'hir>], hir::Expr<'hir>) {
|
||||
let mut parameters: Vec<hir::Param<'_>> = Vec::new();
|
||||
let mut statements: Vec<hir::Stmt<'_>> = Vec::new();
|
||||
|
||||
// Async function parameters are lowered into the closure body so that they are
|
||||
// captured and so that the drop order matches the equivalent non-async functions.
|
||||
//
|
||||
// from:
|
||||
//
|
||||
// async fn foo(<pattern>: <ty>, <pattern>: <ty>, <pattern>: <ty>) {
|
||||
// <body>
|
||||
// }
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// fn foo(__arg0: <ty>, __arg1: <ty>, __arg2: <ty>) {
|
||||
// async move {
|
||||
// let __arg2 = __arg2;
|
||||
// let <pattern> = __arg2;
|
||||
// let __arg1 = __arg1;
|
||||
// let <pattern> = __arg1;
|
||||
// let __arg0 = __arg0;
|
||||
// let <pattern> = __arg0;
|
||||
// drop-temps { <body> } // see comments later in fn for details
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If `<pattern>` is a simple ident, then it is lowered to a single
|
||||
// `let <pattern> = <pattern>;` statement as an optimization.
|
||||
//
|
||||
// Note that the body is embedded in `drop-temps`; an
|
||||
// equivalent desugaring would be `return { <body>
|
||||
// };`. The key point is that we wish to drop all the
|
||||
// let-bound variables and temporaries created in the body
|
||||
// (and its tail expression!) before we drop the
|
||||
// parameters (c.f. rust-lang/rust#64512).
|
||||
for (index, parameter) in decl.inputs.iter().enumerate() {
|
||||
let parameter = self.lower_param(parameter);
|
||||
let span = parameter.pat.span;
|
||||
|
||||
// Check if this is a binding pattern, if so, we can optimize and avoid adding a
|
||||
// `let <pat> = __argN;` statement. In this case, we do not rename the parameter.
|
||||
let (ident, is_simple_parameter) = match parameter.pat.kind {
|
||||
hir::PatKind::Binding(hir::BindingAnnotation(ByRef::No, _), _, ident, _) => {
|
||||
(ident, true)
|
||||
}
|
||||
// For `ref mut` or wildcard arguments, we can't reuse the binding, but
|
||||
// we can keep the same name for the parameter.
|
||||
// This lets rustdoc render it correctly in documentation.
|
||||
hir::PatKind::Binding(_, _, ident, _) => (ident, false),
|
||||
hir::PatKind::Wild => {
|
||||
(Ident::with_dummy_span(rustc_span::symbol::kw::Underscore), false)
|
||||
}
|
||||
_ => {
|
||||
// Replace the ident for bindings that aren't simple.
|
||||
let name = format!("__arg{index}");
|
||||
let ident = Ident::from_str(&name);
|
||||
|
||||
(ident, false)
|
||||
}
|
||||
};
|
||||
|
||||
let desugared_span = self.mark_span_with_reason(DesugaringKind::Async, span, None);
|
||||
|
||||
// Construct a parameter representing `__argN: <ty>` to replace the parameter of the
|
||||
// async function.
|
||||
//
|
||||
// If this is the simple case, this parameter will end up being the same as the
|
||||
// original parameter, but with a different pattern id.
|
||||
let stmt_attrs = self.attrs.get(¶meter.hir_id.local_id).copied();
|
||||
let (new_parameter_pat, new_parameter_id) = self.pat_ident(desugared_span, ident);
|
||||
let new_parameter = hir::Param {
|
||||
hir_id: parameter.hir_id,
|
||||
pat: new_parameter_pat,
|
||||
ty_span: self.lower_span(parameter.ty_span),
|
||||
span: self.lower_span(parameter.span),
|
||||
};
|
||||
|
||||
if is_simple_parameter {
|
||||
// If this is the simple case, then we only insert one statement that is
|
||||
// `let <pat> = <pat>;`. We re-use the original argument's pattern so that
|
||||
// `HirId`s are densely assigned.
|
||||
let expr = self.expr_ident(desugared_span, ident, new_parameter_id);
|
||||
let stmt = self.stmt_let_pat(
|
||||
stmt_attrs,
|
||||
desugared_span,
|
||||
Some(expr),
|
||||
parameter.pat,
|
||||
hir::LocalSource::AsyncFn,
|
||||
);
|
||||
statements.push(stmt);
|
||||
} else {
|
||||
// If this is not the simple case, then we construct two statements:
|
||||
//
|
||||
// ```
|
||||
// let __argN = __argN;
|
||||
// let <pat> = __argN;
|
||||
// ```
|
||||
//
|
||||
// The first statement moves the parameter into the closure and thus ensures
|
||||
// that the drop order is correct.
|
||||
//
|
||||
// The second statement creates the bindings that the user wrote.
|
||||
|
||||
// Construct the `let mut __argN = __argN;` statement. It must be a mut binding
|
||||
// because the user may have specified a `ref mut` binding in the next
|
||||
// statement.
|
||||
let (move_pat, move_id) =
|
||||
self.pat_ident_binding_mode(desugared_span, ident, hir::BindingAnnotation::MUT);
|
||||
let move_expr = self.expr_ident(desugared_span, ident, new_parameter_id);
|
||||
let move_stmt = self.stmt_let_pat(
|
||||
None,
|
||||
desugared_span,
|
||||
Some(move_expr),
|
||||
move_pat,
|
||||
hir::LocalSource::AsyncFn,
|
||||
);
|
||||
|
||||
// Construct the `let <pat> = __argN;` statement. We re-use the original
|
||||
// parameter's pattern so that `HirId`s are densely assigned.
|
||||
let pattern_expr = self.expr_ident(desugared_span, ident, move_id);
|
||||
let pattern_stmt = self.stmt_let_pat(
|
||||
stmt_attrs,
|
||||
desugared_span,
|
||||
Some(pattern_expr),
|
||||
parameter.pat,
|
||||
hir::LocalSource::AsyncFn,
|
||||
);
|
||||
|
||||
statements.push(move_stmt);
|
||||
statements.push(pattern_stmt);
|
||||
};
|
||||
|
||||
parameters.push(new_parameter);
|
||||
}
|
||||
|
||||
let mkbody = |this: &mut LoweringContext<'_, 'hir>| {
|
||||
// Create a block from the user's function body:
|
||||
let user_body = lower_body(this);
|
||||
|
||||
// Transform into `drop-temps { <user-body> }`, an expression:
|
||||
let desugared_span =
|
||||
this.mark_span_with_reason(DesugaringKind::Async, user_body.span, None);
|
||||
let user_body = this.expr_drop_temps(desugared_span, this.arena.alloc(user_body));
|
||||
|
||||
// As noted above, create the final block like
|
||||
//
|
||||
// ```
|
||||
// {
|
||||
// let $param_pattern = $raw_param;
|
||||
// ...
|
||||
// drop-temps { <user-body> }
|
||||
// }
|
||||
// ```
|
||||
let body = this.block_all(
|
||||
desugared_span,
|
||||
this.arena.alloc_from_iter(statements),
|
||||
Some(user_body),
|
||||
);
|
||||
|
||||
this.expr_block(body)
|
||||
};
|
||||
let desugaring_kind = match coroutine_kind {
|
||||
CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async,
|
||||
CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen,
|
||||
CoroutineKind::AsyncGen { .. } => hir::CoroutineDesugaring::AsyncGen,
|
||||
};
|
||||
let closure_id = coroutine_kind.closure_id();
|
||||
let coroutine_expr = self.make_desugared_coroutine_expr(
|
||||
// FIXME(async_closures): This should only move locals,
|
||||
// and not upvars. Capturing closure upvars by ref doesn't
|
||||
// work right now anyways, so whatever.
|
||||
CaptureBy::Value { move_kw: rustc_span::DUMMY_SP },
|
||||
closure_id,
|
||||
return_type_hint,
|
||||
body_span,
|
||||
desugaring_kind,
|
||||
coroutine_source,
|
||||
mkbody,
|
||||
);
|
||||
|
||||
let expr = hir::Expr {
|
||||
hir_id: self.lower_node_id(closure_id),
|
||||
kind: coroutine_expr,
|
||||
span: self.lower_span(body_span),
|
||||
};
|
||||
|
||||
(self.arena.alloc_from_iter(parameters), expr)
|
||||
}
|
||||
|
||||
fn lower_method_sig(
|
||||
&mut self,
|
||||
generics: &Generics,
|
||||
|
@ -1,12 +1,14 @@
|
||||
#### Note: this error code is no longer emitted by the compiler.
|
||||
|
||||
`async` non-`move` closures with parameters are currently not supported.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```compile_fail,edition2018,E0708
|
||||
```edition2018
|
||||
#![feature(async_closure)]
|
||||
|
||||
fn main() {
|
||||
let add_one = async |num: u8| { // error!
|
||||
let add_one = async |num: u8| {
|
||||
num + 1
|
||||
};
|
||||
}
|
||||
|
@ -293,49 +293,59 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
callee_node: &hir::ExprKind<'_>,
|
||||
callee_span: Span,
|
||||
) {
|
||||
let hir::ExprKind::Block(..) = callee_node else {
|
||||
// Only calls on blocks suggested here.
|
||||
return;
|
||||
};
|
||||
|
||||
let hir = self.tcx.hir();
|
||||
let parent_hir_id = hir.parent_id(hir_id);
|
||||
let parent_node = self.tcx.hir_node(parent_hir_id);
|
||||
if let (
|
||||
hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, kind, .. }),
|
||||
let fn_decl_span = if let hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
|
||||
..
|
||||
}) = hir.get_parent(hir_id)
|
||||
{
|
||||
fn_decl_span
|
||||
} else if let Some((
|
||||
_,
|
||||
hir::Node::Expr(&hir::Expr {
|
||||
hir_id: parent_hir_id,
|
||||
kind:
|
||||
hir::ExprKind::Closure(&hir::Closure {
|
||||
kind:
|
||||
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
|
||||
hir::CoroutineDesugaring::Async,
|
||||
hir::CoroutineSource::Closure,
|
||||
)),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}),
|
||||
hir::ExprKind::Block(..),
|
||||
) = (parent_node, callee_node)
|
||||
)) = hir.parent_iter(hir_id).nth(3)
|
||||
{
|
||||
let fn_decl_span = if matches!(
|
||||
kind,
|
||||
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
|
||||
hir::CoroutineDesugaring::Async,
|
||||
hir::CoroutineSource::Closure
|
||||
),)
|
||||
) {
|
||||
// Actually need to unwrap one more layer of HIR to get to
|
||||
// the _real_ closure...
|
||||
let async_closure = hir.parent_id(parent_hir_id);
|
||||
if let hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
|
||||
..
|
||||
}) = self.tcx.hir_node(async_closure)
|
||||
{
|
||||
fn_decl_span
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Actually need to unwrap one more layer of HIR to get to
|
||||
// the _real_ closure...
|
||||
let async_closure = hir.parent_id(parent_hir_id);
|
||||
if let hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
|
||||
..
|
||||
}) = self.tcx.hir_node(async_closure)
|
||||
{
|
||||
fn_decl_span
|
||||
};
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let start = fn_decl_span.shrink_to_lo();
|
||||
let end = callee_span.shrink_to_hi();
|
||||
err.multipart_suggestion(
|
||||
"if you meant to create this closure and immediately call it, surround the \
|
||||
let start = fn_decl_span.shrink_to_lo();
|
||||
let end = callee_span.shrink_to_hi();
|
||||
err.multipart_suggestion(
|
||||
"if you meant to create this closure and immediately call it, surround the \
|
||||
closure with parentheses",
|
||||
vec![(start, "(".to_string()), (end, ")".to_string())],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
vec![(start, "(".to_string()), (end, ")".to_string())],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
/// Give appropriate suggestion when encountering `[("a", 0) ("b", 1)]`, where the
|
||||
|
@ -45,50 +45,72 @@ declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
// For functions, with explicitly defined types, don't warn.
|
||||
// XXXkhuey maybe we should?
|
||||
if let ExprKind::Closure(Closure {
|
||||
kind:
|
||||
ClosureKind::Coroutine(CoroutineKind::Desugared(
|
||||
CoroutineDesugaring::Async,
|
||||
CoroutineSource::Block | CoroutineSource::Closure,
|
||||
)),
|
||||
let ExprKind::Closure(Closure {
|
||||
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, kind)),
|
||||
body: body_id,
|
||||
..
|
||||
}) = expr.kind
|
||||
{
|
||||
if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() {
|
||||
let typeck_results = cx.tcx.typeck_body(*body_id);
|
||||
let body = cx.tcx.hir().body(*body_id);
|
||||
let expr_ty = typeck_results.expr_ty(body.value);
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
|
||||
let return_expr_span = match &body.value.kind {
|
||||
// XXXkhuey there has to be a better way.
|
||||
ExprKind::Block(block, _) => block.expr.map(|e| e.span),
|
||||
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(return_expr_span) = return_expr_span {
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
ASYNC_YIELDS_ASYNC,
|
||||
body.value.hir_id,
|
||||
let body_expr = match kind {
|
||||
CoroutineSource::Fn => {
|
||||
// For functions, with explicitly defined types, don't warn.
|
||||
// XXXkhuey maybe we should?
|
||||
return;
|
||||
},
|
||||
CoroutineSource::Block => cx.tcx.hir().body(*body_id).value,
|
||||
CoroutineSource::Closure => {
|
||||
// Like `async fn`, async closures are wrapped in an additional block
|
||||
// to move all of the closure's arguments into the future.
|
||||
|
||||
let async_closure_body = cx.tcx.hir().body(*body_id).value;
|
||||
let ExprKind::Block(block, _) = async_closure_body.kind else {
|
||||
return;
|
||||
};
|
||||
let Some(block_expr) = block.expr else {
|
||||
return;
|
||||
};
|
||||
let ExprKind::DropTemps(body_expr) = block_expr.kind else {
|
||||
return;
|
||||
};
|
||||
body_expr
|
||||
},
|
||||
};
|
||||
|
||||
let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let typeck_results = cx.tcx.typeck_body(*body_id);
|
||||
let expr_ty = typeck_results.expr_ty(body_expr);
|
||||
|
||||
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
|
||||
let return_expr_span = match &body_expr.kind {
|
||||
// XXXkhuey there has to be a better way.
|
||||
ExprKind::Block(block, _) => block.expr.map(|e| e.span),
|
||||
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(return_expr_span) = return_expr_span {
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
ASYNC_YIELDS_ASYNC,
|
||||
body_expr.hir_id,
|
||||
return_expr_span,
|
||||
"an async construct yields a type which is itself awaitable",
|
||||
|db| {
|
||||
db.span_label(body_expr.span, "outer async construct");
|
||||
db.span_label(return_expr_span, "awaitable value not awaited");
|
||||
db.span_suggestion(
|
||||
return_expr_span,
|
||||
"an async construct yields a type which is itself awaitable",
|
||||
|db| {
|
||||
db.span_label(body.value.span, "outer async construct");
|
||||
db.span_label(return_expr_span, "awaitable value not awaited");
|
||||
db.span_suggestion(
|
||||
return_expr_span,
|
||||
"consider awaiting this value",
|
||||
format!("{}.await", snippet(cx, return_expr_span, "..")),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
"consider awaiting this value",
|
||||
format!("{}.await", snippet(cx, return_expr_span, "..")),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ use clippy_utils::sugg::Sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
|
||||
use rustc_hir::{intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Node};
|
||||
use rustc_hir::{
|
||||
intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, Node,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
@ -166,10 +168,22 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
||||
if coroutine_kind.is_async()
|
||||
&& let hir::ExprKind::Closure(closure) = body.kind
|
||||
{
|
||||
let async_closure_body = cx.tcx.hir().body(closure.body);
|
||||
// Like `async fn`, async closures are wrapped in an additional block
|
||||
// to move all of the closure's arguments into the future.
|
||||
|
||||
let async_closure_body = cx.tcx.hir().body(closure.body).value;
|
||||
let ExprKind::Block(block, _) = async_closure_body.kind else {
|
||||
return;
|
||||
};
|
||||
let Some(block_expr) = block.expr else {
|
||||
return;
|
||||
};
|
||||
let ExprKind::DropTemps(body_expr) = block_expr.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
// `async x` is a syntax error, so it becomes `async { x }`
|
||||
if !matches!(async_closure_body.value.kind, hir::ExprKind::Block(_, _)) {
|
||||
if !matches!(body_expr.kind, hir::ExprKind::Block(_, _)) {
|
||||
hint = hint.blockify();
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,11 @@ if let ExprKind::Closure { capture_clause: CaptureBy::Value { .. }, fn_decl: fn_
|
||||
&& expr2 = &cx.tcx.hir().body(body_id1).value
|
||||
&& let ExprKind::Block(block, None) = expr2.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& block.expr.is_none()
|
||||
&& let Some(trailing_expr) = block.expr
|
||||
&& let ExprKind::DropTemps(expr3) = trailing_expr.kind
|
||||
&& let ExprKind::Block(block1, None) = expr3.kind
|
||||
&& block1.stmts.is_empty()
|
||||
&& block1.expr.is_none()
|
||||
{
|
||||
// report your lint here
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
// edition:2018
|
||||
// check-pass
|
||||
|
||||
#![feature(async_closure)]
|
||||
fn foo() -> Box<dyn std::future::Future<Output = u32>> {
|
||||
let x = 0u32;
|
||||
Box::new((async || x)())
|
||||
//~^ ERROR E0373
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -1,21 +0,0 @@
|
||||
error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
|
||||
--> $DIR/async-borrowck-escaping-closure-error.rs:5:15
|
||||
|
|
||||
LL | Box::new((async || x)())
|
||||
| ^^^^^^^^ - `x` is borrowed here
|
||||
| |
|
||||
| may outlive borrowed value `x`
|
||||
|
|
||||
note: closure is returned here
|
||||
--> $DIR/async-borrowck-escaping-closure-error.rs:5:5
|
||||
|
|
||||
LL | Box::new((async || x)())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
|
||||
|
|
||||
LL | Box::new((async move || x)())
|
||||
| ++++
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0373`.
|
@ -1,8 +1,8 @@
|
||||
// edition:2018
|
||||
// check-pass
|
||||
|
||||
#![feature(async_closure)]
|
||||
|
||||
fn main() {
|
||||
let _ = async |x: u8| {};
|
||||
//~^ ERROR `async` non-`move` closures with parameters are not currently supported
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
error[E0708]: `async` non-`move` closures with parameters are not currently supported
|
||||
--> $DIR/no-params-non-move-async-closure.rs:6:13
|
||||
|
|
||||
LL | let _ = async |x: u8| {};
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider using `let` statements to manually capture variables by reference before entering an `async move` closure
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0708`.
|
Loading…
x
Reference in New Issue
Block a user