Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement await-outside-async / E1142 #972

Merged
merged 1 commit into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F634 | IfTuple | If test is a tuple, which is always `True` | |
| F701 | BreakOutsideLoop | `break` outside loop | |
| F702 | ContinueOutsideLoop | `continue` not properly in loop | |
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function | |
| F704 | YieldOutsideFunction | `yield` statement outside of a function | |
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
| F707 | DefaultExceptNotLast | An `except` block as not the last exception handler | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
Expand Down Expand Up @@ -720,6 +720,14 @@ For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |

### Pylint

For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.

| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PLE1142 | AwaitOutsideAsync | `await` should be used within an async function | |

### Ruff-specific rules

| Code | Name | Message | Fix |
Expand Down
30 changes: 30 additions & 0 deletions resources/test/fixtures/pylint/await_outside_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# pylint: disable=missing-docstring,unused-variable
import asyncio

async def nested():
return 42

async def main():
nested()
print(await nested()) # This is okay

def not_async():
print(await nested()) # [await-outside-async]


async def func(i):
return i**2

async def okay_function():
var = [await func(i) for i in range(5)] # This should be okay


# Test nested functions
async def func2():
def inner_func():
await asyncio.sleep(1) # [await-outside-async]


def outer_func():
async def inner_func():
await asyncio.sleep(1)
26 changes: 14 additions & 12 deletions ruff_dev/src/generate_check_code_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,19 @@ pub fn main(cli: &Cli) -> Result<()> {
gen = gen.push_variant(Variant::new(prefix.to_string()));
}

// Create the `PrefixSpecificity` definition.
// Create the `SuffixLength` definition.
scope
.new_enum("PrefixSpecificity")
.new_enum("SuffixLength")
.vis("pub")
.derive("PartialEq")
.derive("Eq")
.derive("PartialOrd")
.derive("Ord")
.push_variant(Variant::new("Category"))
.push_variant(Variant::new("Hundreds"))
.push_variant(Variant::new("Tens"))
.push_variant(Variant::new("Explicit"));
.push_variant(Variant::new("Zero"))
.push_variant(Variant::new("One"))
.push_variant(Variant::new("Two"))
.push_variant(Variant::new("Three"))
.push_variant(Variant::new("Four"));

// Create the `match` statement, to map from definition to relevant codes.
let mut gen = scope
Expand All @@ -95,21 +96,22 @@ pub fn main(cli: &Cli) -> Result<()> {
.new_impl("CheckCodePrefix")
.new_fn("specificity")
.arg_ref_self()
.ret(Type::new("PrefixSpecificity"))
.ret(Type::new("SuffixLength"))
.vis("pub")
.line("#[allow(clippy::match_same_arms)]")
.line("match self {");
for prefix in prefix_to_codes.keys() {
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
let specificity = match num_numeric {
3 => "Explicit",
2 => "Tens",
1 => "Hundreds",
0 => "Category",
0 => "Zero",
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
_ => panic!("Invalid prefix: {prefix}"),
};
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
"CheckCodePrefix::{prefix} => SuffixLength::{},",
specificity
));
}
Expand Down
5 changes: 3 additions & 2 deletions src/ast/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ impl Range {
}
}

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct FunctionScope {
pub async_: bool,
pub uses_locals: bool,
}

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct ClassScope<'a> {
pub name: &'a str,
pub bases: &'a [Expr],
Expand Down
27 changes: 14 additions & 13 deletions src/check_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes,
pyupgrade, rules,
pylint, pyupgrade, rules,
};

const GLOBAL_SCOPE_INDEX: usize = 0;
Expand Down Expand Up @@ -1696,8 +1696,8 @@ where
}
}
ExprKind::Yield { .. } => {
let scope = self.current_scope();
if self.settings.enabled.contains(&CheckCode::F704) {
let scope = self.current_scope();
if matches!(scope.kind, ScopeKind::Class(_) | ScopeKind::Module) {
self.add_check(Check::new(
CheckKind::YieldOutsideFunction(DeferralKeyword::Yield),
Expand All @@ -1707,8 +1707,8 @@ where
}
}
ExprKind::YieldFrom { .. } => {
let scope = self.current_scope();
if self.settings.enabled.contains(&CheckCode::F704) {
let scope = self.current_scope();
if matches!(scope.kind, ScopeKind::Class(_) | ScopeKind::Module) {
self.add_check(Check::new(
CheckKind::YieldOutsideFunction(DeferralKeyword::YieldFrom),
Expand All @@ -1718,15 +1718,18 @@ where
}
}
ExprKind::Await { .. } => {
let scope = self.current_scope();
if self.settings.enabled.contains(&CheckCode::F704) {
let scope = self.current_scope();
if matches!(scope.kind, ScopeKind::Class(_) | ScopeKind::Module) {
self.add_check(Check::new(
CheckKind::YieldOutsideFunction(DeferralKeyword::Await),
Range::from_located(expr),
));
}
}
if self.settings.enabled.contains(&CheckCode::PLE1142) {
pylint::plugins::await_outside_async(self, expr);
}
}
ExprKind::JoinedStr { values } => {
if self.settings.enabled.contains(&CheckCode::F541) {
Expand Down Expand Up @@ -2819,7 +2822,10 @@ impl<'a> Checker<'a> {
self.parent_stack = parents;
self.scope_stack = scopes;
self.visible_scope = visibility;
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope::default())));
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope {
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
uses_locals: false,
})));

match &stmt.node {
StmtKind::FunctionDef { body, args, .. }
Expand Down Expand Up @@ -2945,14 +2951,9 @@ impl<'a> Checker<'a> {
BTreeMap::new();

for (name, binding) in &scope.values {
let (full_name, context) = match &binding.kind {
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context)
| BindingKind::FromImportation(_, full_name, context) => {
(full_name, context)
}
_ => continue,
};
let (BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context)
| BindingKind::FromImportation(_, full_name, context)) = &binding.kind else { continue };

// Skip used exports from `__all__`
if binding.used.is_some()
Expand Down
16 changes: 16 additions & 0 deletions src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ pub enum CheckCode {
F831,
F841,
F901,
// pylint errors
PLE1142,
// flake8-builtins
A001,
A002,
Expand Down Expand Up @@ -300,6 +302,7 @@ pub enum CheckCategory {
Flake82020,
Flake8BlindExcept,
McCabe,
Pylint,
Ruff,
Meta,
}
Expand Down Expand Up @@ -327,6 +330,7 @@ impl CheckCategory {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Pylint => "Pylint",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Ruff => "Ruff-specific rules",
}
Expand Down Expand Up @@ -370,6 +374,7 @@ impl CheckCategory {
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Pylint => Some("https://pypi.org/project/pylint/2.15.7/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Ruff => None,
}
Expand Down Expand Up @@ -471,6 +476,8 @@ pub enum CheckKind {
UnusedImport(String, bool),
UnusedVariable(String),
YieldOutsideFunction(DeferralKeyword),
// pylint errors
AwaitOutsideAsync,
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
Expand Down Expand Up @@ -752,6 +759,8 @@ impl CheckCode {
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// pylint errors
CheckCode::PLE1142 => CheckKind::AwaitOutsideAsync,
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
Expand Down Expand Up @@ -1160,6 +1169,7 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::PLE1142 => CheckCategory::Pylint,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
Expand Down Expand Up @@ -1271,6 +1281,8 @@ impl CheckKind {
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
// pylint errors
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
Expand Down Expand Up @@ -1640,6 +1652,10 @@ impl CheckKind {
CheckKind::InvalidEscapeSequence(char) => {
format!("Invalid escape sequence: '\\{char}'")
}
// pylint errors
CheckKind::AwaitOutsideAsync => {
"`await` should be used within an async function".to_string()
}
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
Expand Down
Loading