Skip to content

Commit

Permalink
Auto merge of rust-lang#17791 - ShoyuVanilla:await-outside-of-async, …
Browse files Browse the repository at this point in the history
…r=Veykril

feat: Implement diagnostic for `await` outside of `async`

Closes rust-lang#17781
  • Loading branch information
bors committed Aug 5, 2024
2 parents 22f7c08 + 3e809f8 commit 81cbc78
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/tools/rust-analyzer/crates/hir-def/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub enum BodyDiagnostic {
MacroError { node: InFile<AstPtr<ast::MacroCall>>, err: ExpandError },
UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
AwaitOutsideOfAsync { node: InFile<AstPtr<ast::AwaitExpr>>, location: String },
UndeclaredLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
}

Expand Down
90 changes: 73 additions & 17 deletions src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub(super) fn lower(
is_lowering_coroutine: false,
label_ribs: Vec::new(),
current_binding_owner: None,
awaitable_context: None,
}
.collect(params, body, is_async_fn)
}
Expand Down Expand Up @@ -100,6 +101,8 @@ struct ExprCollector<'a> {
// resolution
label_ribs: Vec<LabelRib>,
current_binding_owner: Option<ExprId>,

awaitable_context: Option<Awaitable>,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -135,6 +138,11 @@ impl RibKind {
}
}

enum Awaitable {
Yes,
No(&'static str),
}

#[derive(Debug, Default)]
struct BindingList {
map: FxHashMap<Name, BindingId>,
Expand Down Expand Up @@ -180,6 +188,18 @@ impl ExprCollector<'_> {
body: Option<ast::Expr>,
is_async_fn: bool,
) -> (Body, BodySourceMap) {
self.awaitable_context.replace(if is_async_fn {
Awaitable::Yes
} else {
match self.owner {
DefWithBodyId::FunctionId(..) => Awaitable::No("non-async function"),
DefWithBodyId::StaticId(..) => Awaitable::No("static"),
DefWithBodyId::ConstId(..) | DefWithBodyId::InTypeConstId(..) => {
Awaitable::No("constant")
}
DefWithBodyId::VariantId(..) => Awaitable::No("enum variant"),
}
});
if let Some((param_list, mut attr_enabled)) = param_list {
let mut params = vec![];
if let Some(self_param) =
Expand Down Expand Up @@ -280,31 +300,40 @@ impl ExprCollector<'_> {
}
Some(ast::BlockModifier::Async(_)) => {
self.with_label_rib(RibKind::Closure, |this| {
this.collect_block_(e, |id, statements, tail| Expr::Async {
id,
statements,
tail,
this.with_awaitable_block(Awaitable::Yes, |this| {
this.collect_block_(e, |id, statements, tail| Expr::Async {
id,
statements,
tail,
})
})
})
}
Some(ast::BlockModifier::Const(_)) => {
self.with_label_rib(RibKind::Constant, |this| {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let inner_expr = this.collect_block(e);
let it = this.db.intern_anonymous_const(ConstBlockLoc {
parent: this.owner,
root: inner_expr,
});
this.body.exprs[result_expr_id] = Expr::Const(it);
this.current_binding_owner = prev_binding_owner;
result_expr_id
this.with_awaitable_block(Awaitable::No("constant block"), |this| {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let inner_expr = this.collect_block(e);
let it = this.db.intern_anonymous_const(ConstBlockLoc {
parent: this.owner,
root: inner_expr,
});
this.body.exprs[result_expr_id] = Expr::Const(it);
this.current_binding_owner = prev_binding_owner;
result_expr_id
})
})
}
// FIXME
Some(ast::BlockModifier::AsyncGen(_)) | Some(ast::BlockModifier::Gen(_)) | None => {
self.collect_block(e)
Some(ast::BlockModifier::AsyncGen(_)) => {
self.with_awaitable_block(Awaitable::Yes, |this| this.collect_block(e))
}
Some(ast::BlockModifier::Gen(_)) => self
.with_awaitable_block(Awaitable::No("non-async gen block"), |this| {
this.collect_block(e)
}),
None => self.collect_block(e),
},
ast::Expr::LoopExpr(e) => {
let label = e.label().map(|label| self.collect_label(label));
Expand Down Expand Up @@ -469,6 +498,12 @@ impl ExprCollector<'_> {
}
ast::Expr::AwaitExpr(e) => {
let expr = self.collect_expr_opt(e.expr());
if let Awaitable::No(location) = self.is_lowering_awaitable_block() {
self.source_map.diagnostics.push(BodyDiagnostic::AwaitOutsideOfAsync {
node: InFile::new(self.expander.current_file_id(), AstPtr::new(&e)),
location: location.to_string(),
});
}
self.alloc_expr(Expr::Await { expr }, syntax_ptr)
}
ast::Expr::TryExpr(e) => self.collect_try_operator(syntax_ptr, e),
Expand Down Expand Up @@ -527,7 +562,13 @@ impl ExprCollector<'_> {
let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine);
let prev_try_block_label = this.current_try_block_label.take();

let body = this.collect_expr_opt(e.body());
let awaitable = if e.async_token().is_some() {
Awaitable::Yes
} else {
Awaitable::No("non-async closure")
};
let body =
this.with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body()));

let closure_kind = if this.is_lowering_coroutine {
let movability = if e.static_token().is_some() {
Expand Down Expand Up @@ -2082,6 +2123,21 @@ impl ExprCollector<'_> {
fn alloc_label_desugared(&mut self, label: Label) -> LabelId {
self.body.labels.alloc(label)
}

fn is_lowering_awaitable_block(&self) -> &Awaitable {
self.awaitable_context.as_ref().unwrap_or(&Awaitable::No("unknown"))
}

fn with_awaitable_block<T>(
&mut self,
awaitable: Awaitable,
f: impl FnOnce(&mut Self) -> T,
) -> T {
let orig = self.awaitable_context.replace(awaitable);
let res = f(self);
self.awaitable_context = orig;
res
}
}

fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool {
Expand Down
7 changes: 7 additions & 0 deletions src/tools/rust-analyzer/crates/hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ macro_rules! diagnostics {
// ]

diagnostics![
AwaitOutsideOfAsync,
BreakOutsideOfLoop,
ExpectedFunction,
InactiveCode,
Expand Down Expand Up @@ -135,6 +136,12 @@ pub struct UnreachableLabel {
pub name: Name,
}

#[derive(Debug)]
pub struct AwaitOutsideOfAsync {
pub node: InFile<AstPtr<ast::AwaitExpr>>,
pub location: String,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UndeclaredLabel {
pub node: InFile<AstPtr<ast::Lifetime>>,
Expand Down
3 changes: 3 additions & 0 deletions src/tools/rust-analyzer/crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,9 @@ impl DefWithBody {
is_bang: true,
}
.into(),
BodyDiagnostic::AwaitOutsideOfAsync { node, location } => {
AwaitOutsideOfAsync { node: *node, location: location.clone() }.into()
}
BodyDiagnostic::UnreachableLabel { node, name } => {
UnreachableLabel { node: *node, name: name.clone() }.into()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::{adjusted_display_range, Diagnostic, DiagnosticsContext};

// Diagnostic: await-outside-of-async
//
// This diagnostic is triggered if the `await` keyword is used outside of an async function or block
pub(crate) fn await_outside_of_async(
ctx: &DiagnosticsContext<'_>,
d: &hir::AwaitOutsideOfAsync,
) -> Diagnostic {
let display_range =
adjusted_display_range(ctx, d.node, &|node| Some(node.await_token()?.text_range()));
Diagnostic::new(
crate::DiagnosticCode::RustcHardError("E0728"),
format!("`await` is used inside {}, which is not an `async` context", d.location),
display_range,
)
}

#[cfg(test)]
mod tests {
use crate::tests::check_diagnostics;

#[test]
fn await_inside_non_async_fn() {
check_diagnostics(
r#"
async fn foo() {}
fn bar() {
foo().await;
//^^^^^ error: `await` is used inside non-async function, which is not an `async` context
}
"#,
);
}

#[test]
fn await_inside_async_fn() {
check_diagnostics(
r#"
async fn foo() {}
async fn bar() {
foo().await;
}
"#,
);
}

#[test]
fn await_inside_closure() {
check_diagnostics(
r#"
async fn foo() {}
async fn bar() {
let _a = || { foo().await };
//^^^^^ error: `await` is used inside non-async closure, which is not an `async` context
}
"#,
);
}

#[test]
fn await_inside_async_block() {
check_diagnostics(
r#"
async fn foo() {}
fn bar() {
let _a = async { foo().await };
}
"#,
);
}

#[test]
fn await_in_complex_context() {
check_diagnostics(
r#"
async fn foo() {}
fn bar() {
async fn baz() {
let a = foo().await;
}
let x = || {
let y = async {
baz().await;
let z = || {
baz().await;
//^^^^^ error: `await` is used inside non-async closure, which is not an `async` context
};
};
};
}
"#,
);
}
}
2 changes: 2 additions & 0 deletions src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
//! don't yet have a great pattern for how to do them properly.

mod handlers {
pub(crate) mod await_outside_of_async;
pub(crate) mod break_outside_of_loop;
pub(crate) mod expected_function;
pub(crate) mod inactive_code;
Expand Down Expand Up @@ -379,6 +380,7 @@ pub fn semantic_diagnostics(

for diag in diags {
let d = match diag {
AnyDiagnostic::AwaitOutsideOfAsync(d) => handlers::await_outside_of_async::await_outside_of_async(&ctx, &d),
AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d),
AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
Some(it) => it,
Expand Down

0 comments on commit 81cbc78

Please sign in to comment.