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

[flake8-return] Take NoReturn annotation into account when analyzing implicit returns #9636

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 48 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,46 @@ 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()
26 changes: 25 additions & 1 deletion crates/ruff_linter/src/rules/flake8_return/rules/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,30 @@ fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
}
}

fn is_func_def_annotated_with_noreturn(func: &Expr, semantic: &SemanticModel) -> bool {
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(return_expr) = returns.as_ref() else {
return false;
};

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

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

/// Return `true` if the `func` is a known function that never returns.
fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(func).is_some_and(|call_path| {
Expand Down Expand Up @@ -455,7 +479,7 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
if matches!(
value.as_ref(),
Expr::Call(ast::ExprCall { func, .. })
if is_noreturn_func(func, checker.semantic())
if is_noreturn_func(func, checker.semantic()) || is_func_def_annotated_with_noreturn(func, checker.semantic())
) => {}
_ => {
let mut diagnostic = Diagnostic::new(ImplicitReturn, stmt.range());
Expand Down