Allow top-level await in Jupyter notebooks (#6607)

## Summary

Top-level `await` is allowed in Jupyter notebooks (see:
[autoawait](https://ipython.readthedocs.io/en/stable/interactive/autoawait.html)).

Closes https://github.com/astral-sh/ruff/issues/6584.

## Test Plan

Had to test this manually. Created a notebook, verified that the `yield`
was flagged but the `await` was not.

<img width="868" alt="Screen Shot 2023-08-15 at 11 40 19 PM"
src="b2853651-30a6-4dc6-851c-9fe7f694b8e8">
This commit is contained in:
Charlie Marsh 2023-08-15 23:59:05 -04:00 committed by GitHub
parent d9a81f4fbb
commit 2d86e78bfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,10 +1,8 @@
use std::fmt; use std::fmt;
use ruff_python_ast::{Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::ScopeKind; use ruff_python_ast::{Expr, Ranged};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -26,12 +24,15 @@ impl fmt::Display for DeferralKeyword {
} }
/// ## What it does /// ## What it does
/// Checks for `yield` and `yield from` statements outside of functions. /// Checks for `yield`, `yield from`, and `await` usages outside of functions.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// The use of a `yield` or `yield from` statement outside of a function will /// The use of `yield`, `yield from`, or `await` outside of a function will
/// raise a `SyntaxError`. /// raise a `SyntaxError`.
/// ///
/// As an exception, `await` is allowed at the top level of a Jupyter notebook
/// (see: [autoawait]).
///
/// ## Example /// ## Example
/// ```python /// ```python
/// class Foo: /// class Foo:
@ -40,6 +41,8 @@ impl fmt::Display for DeferralKeyword {
/// ///
/// ## References /// ## References
/// - [Python documentation: `yield`](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) /// - [Python documentation: `yield`](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement)
///
/// [autoawait]: https://ipython.readthedocs.io/en/stable/interactive/autoawait.html
#[violation] #[violation]
pub struct YieldOutsideFunction { pub struct YieldOutsideFunction {
keyword: DeferralKeyword, keyword: DeferralKeyword,
@ -53,17 +56,26 @@ impl Violation for YieldOutsideFunction {
} }
} }
/// F704
pub(crate) fn yield_outside_function(checker: &mut Checker, expr: &Expr) { pub(crate) fn yield_outside_function(checker: &mut Checker, expr: &Expr) {
if matches!( let scope = checker.semantic().current_scope();
checker.semantic().current_scope().kind, if scope.kind.is_module() || scope.kind.is_class() {
ScopeKind::Class(_) | ScopeKind::Module
) {
let keyword = match expr { let keyword = match expr {
Expr::Yield(_) => DeferralKeyword::Yield, Expr::Yield(_) => DeferralKeyword::Yield,
Expr::YieldFrom(_) => DeferralKeyword::YieldFrom, Expr::YieldFrom(_) => DeferralKeyword::YieldFrom,
Expr::Await(_) => DeferralKeyword::Await, Expr::Await(_) => DeferralKeyword::Await,
_ => return, _ => return,
}; };
// `await` is allowed at the top level of a Jupyter notebook.
// See: https://ipython.readthedocs.io/en/stable/interactive/autoawait.html.
if scope.kind.is_module()
&& checker.source_type.is_jupyter()
&& keyword == DeferralKeyword::Await
{
return;
}
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
YieldOutsideFunction { keyword }, YieldOutsideFunction { keyword },
expr.range(), expr.range(),