Skip to content

Commit 5ccc21a

Browse files
Add support for NoReturn in auto-return-typing (#9206)
## Summary Given a function like: ```python def func(x: int): if not x: raise ValueError else: raise TypeError ``` We now correctly use `NoReturn` as the return type, rather than `None`. Closes #9201.
1 parent f5d4019 commit 5ccc21a

File tree

5 files changed

+406
-150
lines changed

5 files changed

+406
-150
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py

+30
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,33 @@ def method(self):
182182
return 1
183183
else:
184184
return 1.5
185+
186+
187+
def func(x: int):
188+
try:
189+
pass
190+
except:
191+
return 2
192+
193+
194+
def func(x: int):
195+
try:
196+
pass
197+
except:
198+
return 2
199+
else:
200+
return 3
201+
202+
203+
def func(x: int):
204+
if not x:
205+
raise ValueError
206+
else:
207+
raise TypeError
208+
209+
210+
def func(x: int):
211+
if not x:
212+
raise ValueError
213+
else:
214+
return 1

crates/ruff_linter/src/rules/flake8_annotations/helpers.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustc_hash::FxHashSet;
33

44
use ruff_diagnostics::Edit;
55
use ruff_python_ast::helpers::{
6-
implicit_return, pep_604_union, typing_optional, typing_union, ReturnStatementVisitor,
6+
pep_604_union, typing_optional, typing_union, ReturnStatementVisitor, Terminal,
77
};
88
use ruff_python_ast::visitor::Visitor;
99
use ruff_python_ast::{self as ast, Expr, ExprContext};
@@ -57,6 +57,14 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
5757
visitor.returns
5858
};
5959

60+
// Determine the terminal behavior (i.e., implicit return, no return, etc.).
61+
let terminal = Terminal::from_function(function);
62+
63+
// If every control flow path raises an exception, return `NoReturn`.
64+
if terminal == Some(Terminal::Raise) {
65+
return Some(AutoPythonType::NoReturn);
66+
}
67+
6068
// Determine the return type of the first `return` statement.
6169
let Some((return_statement, returns)) = returns.split_first() else {
6270
return Some(AutoPythonType::Atom(PythonType::None));
@@ -80,7 +88,7 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
8088
// if x > 0:
8189
// return 1
8290
// ```
83-
if implicit_return(function) {
91+
if terminal.is_none() {
8492
return_type = return_type.union(ResolvedPythonType::Atom(PythonType::None));
8593
}
8694

@@ -94,6 +102,7 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
94102

95103
#[derive(Debug)]
96104
pub(crate) enum AutoPythonType {
105+
NoReturn,
97106
Atom(PythonType),
98107
Union(FxHashSet<PythonType>),
99108
}
@@ -111,6 +120,21 @@ impl AutoPythonType {
111120
target_version: PythonVersion,
112121
) -> Option<(Expr, Vec<Edit>)> {
113122
match self {
123+
AutoPythonType::NoReturn => {
124+
let (no_return_edit, binding) = importer
125+
.get_or_import_symbol(
126+
&ImportRequest::import_from("typing", "NoReturn"),
127+
at,
128+
semantic,
129+
)
130+
.ok()?;
131+
let expr = Expr::Name(ast::ExprName {
132+
id: binding,
133+
range: TextRange::default(),
134+
ctx: ExprContext::Load,
135+
});
136+
Some((expr, vec![no_return_edit]))
137+
}
114138
AutoPythonType::Atom(python_type) => {
115139
let expr = type_expr(python_type)?;
116140
Some((expr, vec![]))

crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type.snap

+84
Original file line numberDiff line numberDiff line change
@@ -495,4 +495,88 @@ auto_return_type.py:180:9: ANN201 [*] Missing return type annotation for public
495495
182 182 | return 1
496496
183 183 | else:
497497

498+
auto_return_type.py:187:5: ANN201 [*] Missing return type annotation for public function `func`
499+
|
500+
187 | def func(x: int):
501+
| ^^^^ ANN201
502+
188 | try:
503+
189 | pass
504+
|
505+
= help: Add return type annotation: `int | None`
506+
507+
ℹ Unsafe fix
508+
184 184 | return 1.5
509+
185 185 |
510+
186 186 |
511+
187 |-def func(x: int):
512+
187 |+def func(x: int) -> int | None:
513+
188 188 | try:
514+
189 189 | pass
515+
190 190 | except:
516+
517+
auto_return_type.py:194:5: ANN201 [*] Missing return type annotation for public function `func`
518+
|
519+
194 | def func(x: int):
520+
| ^^^^ ANN201
521+
195 | try:
522+
196 | pass
523+
|
524+
= help: Add return type annotation: `int`
525+
526+
ℹ Unsafe fix
527+
191 191 | return 2
528+
192 192 |
529+
193 193 |
530+
194 |-def func(x: int):
531+
194 |+def func(x: int) -> int:
532+
195 195 | try:
533+
196 196 | pass
534+
197 197 | except:
535+
536+
auto_return_type.py:203:5: ANN201 [*] Missing return type annotation for public function `func`
537+
|
538+
203 | def func(x: int):
539+
| ^^^^ ANN201
540+
204 | if not x:
541+
205 | raise ValueError
542+
|
543+
= help: Add return type annotation: `NoReturn`
544+
545+
ℹ Unsafe fix
546+
151 151 |
547+
152 152 | import abc
548+
153 153 | from abc import abstractmethod
549+
154 |+from typing import NoReturn
550+
154 155 |
551+
155 156 |
552+
156 157 | class Foo(abc.ABC):
553+
--------------------------------------------------------------------------------
554+
200 201 | return 3
555+
201 202 |
556+
202 203 |
557+
203 |-def func(x: int):
558+
204 |+def func(x: int) -> NoReturn:
559+
204 205 | if not x:
560+
205 206 | raise ValueError
561+
206 207 | else:
562+
563+
auto_return_type.py:210:5: ANN201 [*] Missing return type annotation for public function `func`
564+
|
565+
210 | def func(x: int):
566+
| ^^^^ ANN201
567+
211 | if not x:
568+
212 | raise ValueError
569+
|
570+
= help: Add return type annotation: `int`
571+
572+
ℹ Unsafe fix
573+
207 207 | raise TypeError
574+
208 208 |
575+
209 209 |
576+
210 |-def func(x: int):
577+
210 |+def func(x: int) -> int:
578+
211 211 | if not x:
579+
212 212 | raise ValueError
580+
213 213 | else:
581+
498582

crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type_py38.snap

+92
Original file line numberDiff line numberDiff line change
@@ -550,4 +550,96 @@ auto_return_type.py:180:9: ANN201 [*] Missing return type annotation for public
550550
182 182 | return 1
551551
183 183 | else:
552552

553+
auto_return_type.py:187:5: ANN201 [*] Missing return type annotation for public function `func`
554+
|
555+
187 | def func(x: int):
556+
| ^^^^ ANN201
557+
188 | try:
558+
189 | pass
559+
|
560+
= help: Add return type annotation: `Optional[int]`
561+
562+
ℹ Unsafe fix
563+
151 151 |
564+
152 152 | import abc
565+
153 153 | from abc import abstractmethod
566+
154 |+from typing import Optional
567+
154 155 |
568+
155 156 |
569+
156 157 | class Foo(abc.ABC):
570+
--------------------------------------------------------------------------------
571+
184 185 | return 1.5
572+
185 186 |
573+
186 187 |
574+
187 |-def func(x: int):
575+
188 |+def func(x: int) -> Optional[int]:
576+
188 189 | try:
577+
189 190 | pass
578+
190 191 | except:
579+
580+
auto_return_type.py:194:5: ANN201 [*] Missing return type annotation for public function `func`
581+
|
582+
194 | def func(x: int):
583+
| ^^^^ ANN201
584+
195 | try:
585+
196 | pass
586+
|
587+
= help: Add return type annotation: `int`
588+
589+
ℹ Unsafe fix
590+
191 191 | return 2
591+
192 192 |
592+
193 193 |
593+
194 |-def func(x: int):
594+
194 |+def func(x: int) -> int:
595+
195 195 | try:
596+
196 196 | pass
597+
197 197 | except:
598+
599+
auto_return_type.py:203:5: ANN201 [*] Missing return type annotation for public function `func`
600+
|
601+
203 | def func(x: int):
602+
| ^^^^ ANN201
603+
204 | if not x:
604+
205 | raise ValueError
605+
|
606+
= help: Add return type annotation: `NoReturn`
607+
608+
ℹ Unsafe fix
609+
151 151 |
610+
152 152 | import abc
611+
153 153 | from abc import abstractmethod
612+
154 |+from typing import NoReturn
613+
154 155 |
614+
155 156 |
615+
156 157 | class Foo(abc.ABC):
616+
--------------------------------------------------------------------------------
617+
200 201 | return 3
618+
201 202 |
619+
202 203 |
620+
203 |-def func(x: int):
621+
204 |+def func(x: int) -> NoReturn:
622+
204 205 | if not x:
623+
205 206 | raise ValueError
624+
206 207 | else:
625+
626+
auto_return_type.py:210:5: ANN201 [*] Missing return type annotation for public function `func`
627+
|
628+
210 | def func(x: int):
629+
| ^^^^ ANN201
630+
211 | if not x:
631+
212 | raise ValueError
632+
|
633+
= help: Add return type annotation: `int`
634+
635+
ℹ Unsafe fix
636+
207 207 | raise TypeError
637+
208 208 |
638+
209 209 |
639+
210 |-def func(x: int):
640+
210 |+def func(x: int) -> int:
641+
211 211 | if not x:
642+
212 212 | raise ValueError
643+
213 213 | else:
644+
553645

0 commit comments

Comments
 (0)