Skip to content

Commit

Permalink
[flake8-return] Take NoReturn annotation into account when analyz…
Browse files Browse the repository at this point in the history
…ing implicit returns (#9636)

## Summary

When we are analyzing the implicit return rule this change add an
additional check to verify if the call expression has been annotated
with NoReturn type from typing module.

See: #5474

## Test Plan

```bash
cargo test
```
  • Loading branch information
mikeleppane committed Jan 24, 2024
1 parent fc3e266 commit 45628a5
Show file tree
Hide file tree
Showing 3 changed files with 358 additions and 236 deletions.
50 changes: 46 additions & 4 deletions crates/ruff_linter/resources/test/fixtures/flake8_return/RET503.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import _thread
import builtins
import os
import posix
from posix import abort
import sys as std_sys
import typing
import typing_extensions
import _thread
import _winapi
from posix import abort
from typing import NoReturn

import _winapi
import pytest
import typing_extensions
from pytest import xfail as py_xfail

###
Expand Down Expand Up @@ -326,3 +327,44 @@ def end_of_file():
if False:
return 1
x = 2 \



# function return type annotation NoReturn
def foo(x: int) -> int:
def bar() -> NoReturn:
abort()
if x == 5:
return 5
bar()


def foo(string: str) -> str:
def raises(value: str) -> NoReturn:
raise RuntimeError("something went wrong")

match string:
case "a":
return "first"
case "b":
return "second"
case "c":
return "third"
case _:
raises(string)


def foo() -> int:
def baz() -> int:
return 1


def bar() -> NoReturn:
a = 1 + 2
raise AssertionError("Very bad")



if baz() > 3:
return 1
bar()
33 changes: 30 additions & 3 deletions crates/ruff_linter/src/rules/flake8_return/rules/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,11 @@ fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
}
}

/// Return `true` if the `func` is a known function that never returns.
/// Return `true` if the `func` appears to be non-returning.
fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(func).is_some_and(|call_path| {
// First, look for known functions that never return from the standard library and popular
// libraries.
if semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!(
call_path.as_slice(),
["" | "builtins" | "sys" | "_thread" | "pytest", "exit"]
Expand All @@ -379,7 +381,32 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
| ["_winapi", "ExitProcess"]
| ["pytest", "fail" | "skip" | "xfail"]
) || semantic.match_typing_call_path(&call_path, "assert_never")
})
}) {
return true;
}

// Second, look for `NoReturn` annotations on the return type.
let Some(func_binding) = semantic.lookup_attribute(func) else {
return false;
};
let Some(node_id) = semantic.binding(func_binding).source else {
return false;
};

let Stmt::FunctionDef(ast::StmtFunctionDef { returns, .. }) = semantic.statement(node_id)
else {
return false;
};

let Some(returns) = returns.as_ref() else {
return false;
};

let Some(call_path) = semantic.resolve_call_path(returns) else {
return false;
};

semantic.match_typing_call_path(&call_path, "NoReturn")
}

/// RET503
Expand Down

0 comments on commit 45628a5

Please sign in to comment.