Skip to content

Commit 81cbc78

Browse files
committed
Auto merge of rust-lang#17791 - ShoyuVanilla:await-outside-of-async, r=Veykril
feat: Implement diagnostic for `await` outside of `async` Closes rust-lang#17781
2 parents 22f7c08 + 3e809f8 commit 81cbc78

File tree

6 files changed

+187
-17
lines changed

6 files changed

+187
-17
lines changed

src/tools/rust-analyzer/crates/hir-def/src/body.rs

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ pub enum BodyDiagnostic {
118118
MacroError { node: InFile<AstPtr<ast::MacroCall>>, err: ExpandError },
119119
UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
120120
UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
121+
AwaitOutsideOfAsync { node: InFile<AstPtr<ast::AwaitExpr>>, location: String },
121122
UndeclaredLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
122123
}
123124

src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs

+73-17
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub(super) fn lower(
7272
is_lowering_coroutine: false,
7373
label_ribs: Vec::new(),
7474
current_binding_owner: None,
75+
awaitable_context: None,
7576
}
7677
.collect(params, body, is_async_fn)
7778
}
@@ -100,6 +101,8 @@ struct ExprCollector<'a> {
100101
// resolution
101102
label_ribs: Vec<LabelRib>,
102103
current_binding_owner: Option<ExprId>,
104+
105+
awaitable_context: Option<Awaitable>,
103106
}
104107

105108
#[derive(Clone, Debug)]
@@ -135,6 +138,11 @@ impl RibKind {
135138
}
136139
}
137140

141+
enum Awaitable {
142+
Yes,
143+
No(&'static str),
144+
}
145+
138146
#[derive(Debug, Default)]
139147
struct BindingList {
140148
map: FxHashMap<Name, BindingId>,
@@ -180,6 +188,18 @@ impl ExprCollector<'_> {
180188
body: Option<ast::Expr>,
181189
is_async_fn: bool,
182190
) -> (Body, BodySourceMap) {
191+
self.awaitable_context.replace(if is_async_fn {
192+
Awaitable::Yes
193+
} else {
194+
match self.owner {
195+
DefWithBodyId::FunctionId(..) => Awaitable::No("non-async function"),
196+
DefWithBodyId::StaticId(..) => Awaitable::No("static"),
197+
DefWithBodyId::ConstId(..) | DefWithBodyId::InTypeConstId(..) => {
198+
Awaitable::No("constant")
199+
}
200+
DefWithBodyId::VariantId(..) => Awaitable::No("enum variant"),
201+
}
202+
});
183203
if let Some((param_list, mut attr_enabled)) = param_list {
184204
let mut params = vec![];
185205
if let Some(self_param) =
@@ -280,31 +300,40 @@ impl ExprCollector<'_> {
280300
}
281301
Some(ast::BlockModifier::Async(_)) => {
282302
self.with_label_rib(RibKind::Closure, |this| {
283-
this.collect_block_(e, |id, statements, tail| Expr::Async {
284-
id,
285-
statements,
286-
tail,
303+
this.with_awaitable_block(Awaitable::Yes, |this| {
304+
this.collect_block_(e, |id, statements, tail| Expr::Async {
305+
id,
306+
statements,
307+
tail,
308+
})
287309
})
288310
})
289311
}
290312
Some(ast::BlockModifier::Const(_)) => {
291313
self.with_label_rib(RibKind::Constant, |this| {
292-
let (result_expr_id, prev_binding_owner) =
293-
this.initialize_binding_owner(syntax_ptr);
294-
let inner_expr = this.collect_block(e);
295-
let it = this.db.intern_anonymous_const(ConstBlockLoc {
296-
parent: this.owner,
297-
root: inner_expr,
298-
});
299-
this.body.exprs[result_expr_id] = Expr::Const(it);
300-
this.current_binding_owner = prev_binding_owner;
301-
result_expr_id
314+
this.with_awaitable_block(Awaitable::No("constant block"), |this| {
315+
let (result_expr_id, prev_binding_owner) =
316+
this.initialize_binding_owner(syntax_ptr);
317+
let inner_expr = this.collect_block(e);
318+
let it = this.db.intern_anonymous_const(ConstBlockLoc {
319+
parent: this.owner,
320+
root: inner_expr,
321+
});
322+
this.body.exprs[result_expr_id] = Expr::Const(it);
323+
this.current_binding_owner = prev_binding_owner;
324+
result_expr_id
325+
})
302326
})
303327
}
304328
// FIXME
305-
Some(ast::BlockModifier::AsyncGen(_)) | Some(ast::BlockModifier::Gen(_)) | None => {
306-
self.collect_block(e)
329+
Some(ast::BlockModifier::AsyncGen(_)) => {
330+
self.with_awaitable_block(Awaitable::Yes, |this| this.collect_block(e))
307331
}
332+
Some(ast::BlockModifier::Gen(_)) => self
333+
.with_awaitable_block(Awaitable::No("non-async gen block"), |this| {
334+
this.collect_block(e)
335+
}),
336+
None => self.collect_block(e),
308337
},
309338
ast::Expr::LoopExpr(e) => {
310339
let label = e.label().map(|label| self.collect_label(label));
@@ -469,6 +498,12 @@ impl ExprCollector<'_> {
469498
}
470499
ast::Expr::AwaitExpr(e) => {
471500
let expr = self.collect_expr_opt(e.expr());
501+
if let Awaitable::No(location) = self.is_lowering_awaitable_block() {
502+
self.source_map.diagnostics.push(BodyDiagnostic::AwaitOutsideOfAsync {
503+
node: InFile::new(self.expander.current_file_id(), AstPtr::new(&e)),
504+
location: location.to_string(),
505+
});
506+
}
472507
self.alloc_expr(Expr::Await { expr }, syntax_ptr)
473508
}
474509
ast::Expr::TryExpr(e) => self.collect_try_operator(syntax_ptr, e),
@@ -527,7 +562,13 @@ impl ExprCollector<'_> {
527562
let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine);
528563
let prev_try_block_label = this.current_try_block_label.take();
529564

530-
let body = this.collect_expr_opt(e.body());
565+
let awaitable = if e.async_token().is_some() {
566+
Awaitable::Yes
567+
} else {
568+
Awaitable::No("non-async closure")
569+
};
570+
let body =
571+
this.with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body()));
531572

532573
let closure_kind = if this.is_lowering_coroutine {
533574
let movability = if e.static_token().is_some() {
@@ -2082,6 +2123,21 @@ impl ExprCollector<'_> {
20822123
fn alloc_label_desugared(&mut self, label: Label) -> LabelId {
20832124
self.body.labels.alloc(label)
20842125
}
2126+
2127+
fn is_lowering_awaitable_block(&self) -> &Awaitable {
2128+
self.awaitable_context.as_ref().unwrap_or(&Awaitable::No("unknown"))
2129+
}
2130+
2131+
fn with_awaitable_block<T>(
2132+
&mut self,
2133+
awaitable: Awaitable,
2134+
f: impl FnOnce(&mut Self) -> T,
2135+
) -> T {
2136+
let orig = self.awaitable_context.replace(awaitable);
2137+
let res = f(self);
2138+
self.awaitable_context = orig;
2139+
res
2140+
}
20852141
}
20862142

20872143
fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool {

src/tools/rust-analyzer/crates/hir/src/diagnostics.rs

+7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ macro_rules! diagnostics {
4848
// ]
4949

5050
diagnostics![
51+
AwaitOutsideOfAsync,
5152
BreakOutsideOfLoop,
5253
ExpectedFunction,
5354
InactiveCode,
@@ -135,6 +136,12 @@ pub struct UnreachableLabel {
135136
pub name: Name,
136137
}
137138

139+
#[derive(Debug)]
140+
pub struct AwaitOutsideOfAsync {
141+
pub node: InFile<AstPtr<ast::AwaitExpr>>,
142+
pub location: String,
143+
}
144+
138145
#[derive(Debug, Clone, Eq, PartialEq)]
139146
pub struct UndeclaredLabel {
140147
pub node: InFile<AstPtr<ast::Lifetime>>,

src/tools/rust-analyzer/crates/hir/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,9 @@ impl DefWithBody {
18281828
is_bang: true,
18291829
}
18301830
.into(),
1831+
BodyDiagnostic::AwaitOutsideOfAsync { node, location } => {
1832+
AwaitOutsideOfAsync { node: *node, location: location.clone() }.into()
1833+
}
18311834
BodyDiagnostic::UnreachableLabel { node, name } => {
18321835
UnreachableLabel { node: *node, name: name.clone() }.into()
18331836
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use crate::{adjusted_display_range, Diagnostic, DiagnosticsContext};
2+
3+
// Diagnostic: await-outside-of-async
4+
//
5+
// This diagnostic is triggered if the `await` keyword is used outside of an async function or block
6+
pub(crate) fn await_outside_of_async(
7+
ctx: &DiagnosticsContext<'_>,
8+
d: &hir::AwaitOutsideOfAsync,
9+
) -> Diagnostic {
10+
let display_range =
11+
adjusted_display_range(ctx, d.node, &|node| Some(node.await_token()?.text_range()));
12+
Diagnostic::new(
13+
crate::DiagnosticCode::RustcHardError("E0728"),
14+
format!("`await` is used inside {}, which is not an `async` context", d.location),
15+
display_range,
16+
)
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use crate::tests::check_diagnostics;
22+
23+
#[test]
24+
fn await_inside_non_async_fn() {
25+
check_diagnostics(
26+
r#"
27+
async fn foo() {}
28+
29+
fn bar() {
30+
foo().await;
31+
//^^^^^ error: `await` is used inside non-async function, which is not an `async` context
32+
}
33+
"#,
34+
);
35+
}
36+
37+
#[test]
38+
fn await_inside_async_fn() {
39+
check_diagnostics(
40+
r#"
41+
async fn foo() {}
42+
43+
async fn bar() {
44+
foo().await;
45+
}
46+
"#,
47+
);
48+
}
49+
50+
#[test]
51+
fn await_inside_closure() {
52+
check_diagnostics(
53+
r#"
54+
async fn foo() {}
55+
56+
async fn bar() {
57+
let _a = || { foo().await };
58+
//^^^^^ error: `await` is used inside non-async closure, which is not an `async` context
59+
}
60+
"#,
61+
);
62+
}
63+
64+
#[test]
65+
fn await_inside_async_block() {
66+
check_diagnostics(
67+
r#"
68+
async fn foo() {}
69+
70+
fn bar() {
71+
let _a = async { foo().await };
72+
}
73+
"#,
74+
);
75+
}
76+
77+
#[test]
78+
fn await_in_complex_context() {
79+
check_diagnostics(
80+
r#"
81+
async fn foo() {}
82+
83+
fn bar() {
84+
async fn baz() {
85+
let a = foo().await;
86+
}
87+
88+
let x = || {
89+
let y = async {
90+
baz().await;
91+
let z = || {
92+
baz().await;
93+
//^^^^^ error: `await` is used inside non-async closure, which is not an `async` context
94+
};
95+
};
96+
};
97+
}
98+
"#,
99+
);
100+
}
101+
}

src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
//! don't yet have a great pattern for how to do them properly.
2525
2626
mod handlers {
27+
pub(crate) mod await_outside_of_async;
2728
pub(crate) mod break_outside_of_loop;
2829
pub(crate) mod expected_function;
2930
pub(crate) mod inactive_code;
@@ -379,6 +380,7 @@ pub fn semantic_diagnostics(
379380

380381
for diag in diags {
381382
let d = match diag {
383+
AnyDiagnostic::AwaitOutsideOfAsync(d) => handlers::await_outside_of_async::await_outside_of_async(&ctx, &d),
382384
AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d),
383385
AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
384386
Some(it) => it,

0 commit comments

Comments
 (0)