diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py b/crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py index 81ce953ed11e0..ab75a50da712c 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py @@ -63,3 +63,87 @@ def func(x: int): return "str" else: return None + + +def func(x: int): + if x: + return 1 + + +def func(): + x = 1 + + +def func(x: int): + if x > 0: + return 1 + + +def func(x: int): + match x: + case [1, 2, 3]: + return 1 + case 4 as y: + return "foo" + + +def func(x: int): + for i in range(5): + if i > 0: + return 1 + + +def func(x: int): + for i in range(5): + if i > 0: + return 1 + else: + return 4 + + +def func(x: int): + for i in range(5): + if i > 0: + break + else: + return 4 + + +def func(x: int): + try: + pass + except: + return 1 + + +def func(x: int): + try: + pass + except: + return 1 + finally: + return 2 + + +def func(x: int): + try: + pass + except: + return 1 + else: + return 2 + + +def func(x: int): + try: + return 1 + except: + return 2 + else: + pass + + +def func(x: int): + while x > 0: + break + return 1 diff --git a/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs b/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs index 9dc7e896dd106..41cff286d79c5 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs @@ -1,10 +1,9 @@ use itertools::Itertools; -use ruff_diagnostics::Edit; use rustc_hash::FxHashSet; -use crate::importer::{ImportRequest, Importer}; +use ruff_diagnostics::Edit; use ruff_python_ast::helpers::{ - pep_604_union, typing_optional, typing_union, ReturnStatementVisitor, + implicit_return, pep_604_union, typing_optional, typing_union, ReturnStatementVisitor, }; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, ExprContext}; @@ -13,6 +12,7 @@ use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::{Definition, SemanticModel}; use ruff_text_size::{TextRange, TextSize}; +use crate::importer::{ImportRequest, Importer}; use crate::settings::types::PythonVersion; /// Return the name of the function, if it's overloaded. @@ -48,14 +48,19 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option Option 0: + // return 1 + // ``` + if implicit_return(function) { + return_type = return_type.union(ResolvedPythonType::Atom(PythonType::None)); + } + match return_type { ResolvedPythonType::Atom(python_type) => Some(AutoPythonType::Atom(python_type)), ResolvedPythonType::Union(python_types) => Some(AutoPythonType::Union(python_types)), diff --git a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type.snap b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type.snap index 021e1b520766a..6fcbb95d029f6 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type.snap +++ b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type.snap @@ -200,4 +200,231 @@ auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public f 61 61 | return 1 62 62 | elif x > 5: +auto_return_type.py:68:5: ANN201 [*] Missing return type annotation for public function `func` + | +68 | def func(x: int): + | ^^^^ ANN201 +69 | if x: +70 | return 1 + | + = help: Add return type annotation: `int | None` + +ℹ Unsafe fix +65 65 | return None +66 66 | +67 67 | +68 |-def func(x: int): + 68 |+def func(x: int) -> int | None: +69 69 | if x: +70 70 | return 1 +71 71 | + +auto_return_type.py:73:5: ANN201 [*] Missing return type annotation for public function `func` + | +73 | def func(): + | ^^^^ ANN201 +74 | x = 1 + | + = help: Add return type annotation: `None` + +ℹ Unsafe fix +70 70 | return 1 +71 71 | +72 72 | +73 |-def func(): + 73 |+def func() -> None: +74 74 | x = 1 +75 75 | +76 76 | + +auto_return_type.py:77:5: ANN201 [*] Missing return type annotation for public function `func` + | +77 | def func(x: int): + | ^^^^ ANN201 +78 | if x > 0: +79 | return 1 + | + = help: Add return type annotation: `int | None` + +ℹ Unsafe fix +74 74 | x = 1 +75 75 | +76 76 | +77 |-def func(x: int): + 77 |+def func(x: int) -> int | None: +78 78 | if x > 0: +79 79 | return 1 +80 80 | + +auto_return_type.py:82:5: ANN201 [*] Missing return type annotation for public function `func` + | +82 | def func(x: int): + | ^^^^ ANN201 +83 | match x: +84 | case [1, 2, 3]: + | + = help: Add return type annotation: `str | int` + +ℹ Unsafe fix +79 79 | return 1 +80 80 | +81 81 | +82 |-def func(x: int): + 82 |+def func(x: int) -> str | int: +83 83 | match x: +84 84 | case [1, 2, 3]: +85 85 | return 1 + +auto_return_type.py:90:5: ANN201 [*] Missing return type annotation for public function `func` + | +90 | def func(x: int): + | ^^^^ ANN201 +91 | for i in range(5): +92 | if i > 0: + | + = help: Add return type annotation: `int | None` + +ℹ Unsafe fix +87 87 | return "foo" +88 88 | +89 89 | +90 |-def func(x: int): + 90 |+def func(x: int) -> int | None: +91 91 | for i in range(5): +92 92 | if i > 0: +93 93 | return 1 + +auto_return_type.py:96:5: ANN201 [*] Missing return type annotation for public function `func` + | +96 | def func(x: int): + | ^^^^ ANN201 +97 | for i in range(5): +98 | if i > 0: + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +93 93 | return 1 +94 94 | +95 95 | +96 |-def func(x: int): + 96 |+def func(x: int) -> int: +97 97 | for i in range(5): +98 98 | if i > 0: +99 99 | return 1 + +auto_return_type.py:104:5: ANN201 [*] Missing return type annotation for public function `func` + | +104 | def func(x: int): + | ^^^^ ANN201 +105 | for i in range(5): +106 | if i > 0: + | + = help: Add return type annotation: `int | None` + +ℹ Unsafe fix +101 101 | return 4 +102 102 | +103 103 | +104 |-def func(x: int): + 104 |+def func(x: int) -> int | None: +105 105 | for i in range(5): +106 106 | if i > 0: +107 107 | break + +auto_return_type.py:112:5: ANN201 [*] Missing return type annotation for public function `func` + | +112 | def func(x: int): + | ^^^^ ANN201 +113 | try: +114 | pass + | + = help: Add return type annotation: `int | None` + +ℹ Unsafe fix +109 109 | return 4 +110 110 | +111 111 | +112 |-def func(x: int): + 112 |+def func(x: int) -> int | None: +113 113 | try: +114 114 | pass +115 115 | except: + +auto_return_type.py:119:5: ANN201 [*] Missing return type annotation for public function `func` + | +119 | def func(x: int): + | ^^^^ ANN201 +120 | try: +121 | pass + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +116 116 | return 1 +117 117 | +118 118 | +119 |-def func(x: int): + 119 |+def func(x: int) -> int: +120 120 | try: +121 121 | pass +122 122 | except: + +auto_return_type.py:128:5: ANN201 [*] Missing return type annotation for public function `func` + | +128 | def func(x: int): + | ^^^^ ANN201 +129 | try: +130 | pass + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +125 125 | return 2 +126 126 | +127 127 | +128 |-def func(x: int): + 128 |+def func(x: int) -> int: +129 129 | try: +130 130 | pass +131 131 | except: + +auto_return_type.py:137:5: ANN201 [*] Missing return type annotation for public function `func` + | +137 | def func(x: int): + | ^^^^ ANN201 +138 | try: +139 | return 1 + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +134 134 | return 2 +135 135 | +136 136 | +137 |-def func(x: int): + 137 |+def func(x: int) -> int: +138 138 | try: +139 139 | return 1 +140 140 | except: + +auto_return_type.py:146:5: ANN201 [*] Missing return type annotation for public function `func` + | +146 | def func(x: int): + | ^^^^ ANN201 +147 | while x > 0: +148 | break + | + = help: Add return type annotation: `int | None` + +ℹ Unsafe fix +143 143 | pass +144 144 | +145 145 | +146 |-def func(x: int): + 146 |+def func(x: int) -> int | None: +147 147 | while x > 0: +148 148 | break +149 149 | return 1 + diff --git a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type_py38.snap b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type_py38.snap index 0831fcffa3e09..d91484ca3fe71 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type_py38.snap +++ b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__auto_return_type_py38.snap @@ -220,4 +220,266 @@ auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public f 61 62 | return 1 62 63 | elif x > 5: +auto_return_type.py:68:5: ANN201 [*] Missing return type annotation for public function `func` + | +68 | def func(x: int): + | ^^^^ ANN201 +69 | if x: +70 | return 1 + | + = help: Add return type annotation: `Optional[int]` + +ℹ Unsafe fix + 1 |+from typing import Optional +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +65 66 | return None +66 67 | +67 68 | +68 |-def func(x: int): + 69 |+def func(x: int) -> Optional[int]: +69 70 | if x: +70 71 | return 1 +71 72 | + +auto_return_type.py:73:5: ANN201 [*] Missing return type annotation for public function `func` + | +73 | def func(): + | ^^^^ ANN201 +74 | x = 1 + | + = help: Add return type annotation: `None` + +ℹ Unsafe fix +70 70 | return 1 +71 71 | +72 72 | +73 |-def func(): + 73 |+def func() -> None: +74 74 | x = 1 +75 75 | +76 76 | + +auto_return_type.py:77:5: ANN201 [*] Missing return type annotation for public function `func` + | +77 | def func(x: int): + | ^^^^ ANN201 +78 | if x > 0: +79 | return 1 + | + = help: Add return type annotation: `Optional[int]` + +ℹ Unsafe fix + 1 |+from typing import Optional +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +74 75 | x = 1 +75 76 | +76 77 | +77 |-def func(x: int): + 78 |+def func(x: int) -> Optional[int]: +78 79 | if x > 0: +79 80 | return 1 +80 81 | + +auto_return_type.py:82:5: ANN201 [*] Missing return type annotation for public function `func` + | +82 | def func(x: int): + | ^^^^ ANN201 +83 | match x: +84 | case [1, 2, 3]: + | + = help: Add return type annotation: `Union[str | int]` + +ℹ Unsafe fix + 1 |+from typing import Union +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +79 80 | return 1 +80 81 | +81 82 | +82 |-def func(x: int): + 83 |+def func(x: int) -> Union[str | int]: +83 84 | match x: +84 85 | case [1, 2, 3]: +85 86 | return 1 + +auto_return_type.py:90:5: ANN201 [*] Missing return type annotation for public function `func` + | +90 | def func(x: int): + | ^^^^ ANN201 +91 | for i in range(5): +92 | if i > 0: + | + = help: Add return type annotation: `Optional[int]` + +ℹ Unsafe fix + 1 |+from typing import Optional +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +87 88 | return "foo" +88 89 | +89 90 | +90 |-def func(x: int): + 91 |+def func(x: int) -> Optional[int]: +91 92 | for i in range(5): +92 93 | if i > 0: +93 94 | return 1 + +auto_return_type.py:96:5: ANN201 [*] Missing return type annotation for public function `func` + | +96 | def func(x: int): + | ^^^^ ANN201 +97 | for i in range(5): +98 | if i > 0: + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +93 93 | return 1 +94 94 | +95 95 | +96 |-def func(x: int): + 96 |+def func(x: int) -> int: +97 97 | for i in range(5): +98 98 | if i > 0: +99 99 | return 1 + +auto_return_type.py:104:5: ANN201 [*] Missing return type annotation for public function `func` + | +104 | def func(x: int): + | ^^^^ ANN201 +105 | for i in range(5): +106 | if i > 0: + | + = help: Add return type annotation: `Optional[int]` + +ℹ Unsafe fix + 1 |+from typing import Optional +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +101 102 | return 4 +102 103 | +103 104 | +104 |-def func(x: int): + 105 |+def func(x: int) -> Optional[int]: +105 106 | for i in range(5): +106 107 | if i > 0: +107 108 | break + +auto_return_type.py:112:5: ANN201 [*] Missing return type annotation for public function `func` + | +112 | def func(x: int): + | ^^^^ ANN201 +113 | try: +114 | pass + | + = help: Add return type annotation: `Optional[int]` + +ℹ Unsafe fix + 1 |+from typing import Optional +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +109 110 | return 4 +110 111 | +111 112 | +112 |-def func(x: int): + 113 |+def func(x: int) -> Optional[int]: +113 114 | try: +114 115 | pass +115 116 | except: + +auto_return_type.py:119:5: ANN201 [*] Missing return type annotation for public function `func` + | +119 | def func(x: int): + | ^^^^ ANN201 +120 | try: +121 | pass + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +116 116 | return 1 +117 117 | +118 118 | +119 |-def func(x: int): + 119 |+def func(x: int) -> int: +120 120 | try: +121 121 | pass +122 122 | except: + +auto_return_type.py:128:5: ANN201 [*] Missing return type annotation for public function `func` + | +128 | def func(x: int): + | ^^^^ ANN201 +129 | try: +130 | pass + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +125 125 | return 2 +126 126 | +127 127 | +128 |-def func(x: int): + 128 |+def func(x: int) -> int: +129 129 | try: +130 130 | pass +131 131 | except: + +auto_return_type.py:137:5: ANN201 [*] Missing return type annotation for public function `func` + | +137 | def func(x: int): + | ^^^^ ANN201 +138 | try: +139 | return 1 + | + = help: Add return type annotation: `int` + +ℹ Unsafe fix +134 134 | return 2 +135 135 | +136 136 | +137 |-def func(x: int): + 137 |+def func(x: int) -> int: +138 138 | try: +139 139 | return 1 +140 140 | except: + +auto_return_type.py:146:5: ANN201 [*] Missing return type annotation for public function `func` + | +146 | def func(x: int): + | ^^^^ ANN201 +147 | while x > 0: +148 | break + | + = help: Add return type annotation: `Optional[int]` + +ℹ Unsafe fix + 1 |+from typing import Optional +1 2 | def func(): +2 3 | return 1 +3 4 | +-------------------------------------------------------------------------------- +143 144 | pass +144 145 | +145 146 | +146 |-def func(x: int): + 147 |+def func(x: int) -> Optional[int]: +147 148 | while x > 0: +148 149 | break +149 150 | return 1 + diff --git a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__defaults.snap b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__defaults.snap index 8665fb92ce192..9be33117659ce 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__defaults.snap +++ b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__defaults.snap @@ -1,14 +1,24 @@ --- source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs --- -annotation_presence.py:5:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:5:5: ANN201 [*] Missing return type annotation for public function `foo` | 4 | # Error 5 | def foo(a, b): | ^^^ ANN201 6 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +2 2 | from typing_extensions import override +3 3 | +4 4 | # Error +5 |-def foo(a, b): + 5 |+def foo(a, b) -> None: +6 6 | pass +7 7 | +8 8 | annotation_presence.py:5:9: ANN001 Missing type annotation for function argument `a` | @@ -26,14 +36,24 @@ annotation_presence.py:5:12: ANN001 Missing type annotation for function argumen 6 | pass | -annotation_presence.py:10:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:10:5: ANN201 [*] Missing return type annotation for public function `foo` | 9 | # Error 10 | def foo(a: int, b): | ^^^ ANN201 11 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +7 7 | +8 8 | +9 9 | # Error +10 |-def foo(a: int, b): + 10 |+def foo(a: int, b) -> None: +11 11 | pass +12 12 | +13 13 | annotation_presence.py:10:17: ANN001 Missing type annotation for function argument `b` | @@ -51,23 +71,43 @@ annotation_presence.py:15:17: ANN001 Missing type annotation for function argume 16 | pass | -annotation_presence.py:20:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:20:5: ANN201 [*] Missing return type annotation for public function `foo` | 19 | # Error 20 | def foo(a: int, b: int): | ^^^ ANN201 21 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` -annotation_presence.py:25:5: ANN201 Missing return type annotation for public function `foo` +ℹ Unsafe fix +17 17 | +18 18 | +19 19 | # Error +20 |-def foo(a: int, b: int): + 20 |+def foo(a: int, b: int) -> None: +21 21 | pass +22 22 | +23 23 | + +annotation_presence.py:25:5: ANN201 [*] Missing return type annotation for public function `foo` | 24 | # Error 25 | def foo(): | ^^^ ANN201 26 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +22 22 | +23 23 | +24 24 | # Error +25 |-def foo(): + 25 |+def foo() -> None: +26 26 | pass +27 27 | +28 28 | annotation_presence.py:45:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a` | diff --git a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__ignore_fully_untyped.snap b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__ignore_fully_untyped.snap index a93908081cec1..d806d641b4caa 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__ignore_fully_untyped.snap +++ b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__ignore_fully_untyped.snap @@ -1,13 +1,23 @@ --- source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs --- -ignore_fully_untyped.py:24:5: ANN201 Missing return type annotation for public function `error_partially_typed_1` +ignore_fully_untyped.py:24:5: ANN201 [*] Missing return type annotation for public function `error_partially_typed_1` | 24 | def error_partially_typed_1(a: int, b): | ^^^^^^^^^^^^^^^^^^^^^^^ ANN201 25 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +21 21 | pass +22 22 | +23 23 | +24 |-def error_partially_typed_1(a: int, b): + 24 |+def error_partially_typed_1(a: int, b) -> None: +25 25 | pass +26 26 | +27 27 | ignore_fully_untyped.py:24:37: ANN001 Missing type annotation for function argument `b` | @@ -23,15 +33,25 @@ ignore_fully_untyped.py:28:37: ANN001 Missing type annotation for function argum 29 | pass | -ignore_fully_untyped.py:32:5: ANN201 Missing return type annotation for public function `error_partially_typed_3` +ignore_fully_untyped.py:32:5: ANN201 [*] Missing return type annotation for public function `error_partially_typed_3` | 32 | def error_partially_typed_3(a: int, b: int): | ^^^^^^^^^^^^^^^^^^^^^^^ ANN201 33 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +29 29 | pass +30 30 | +31 31 | +32 |-def error_partially_typed_3(a: int, b: int): + 32 |+def error_partially_typed_3(a: int, b: int) -> None: +33 33 | pass +34 34 | +35 35 | -ignore_fully_untyped.py:43:9: ANN201 Missing return type annotation for public function `error_typed_self` +ignore_fully_untyped.py:43:9: ANN201 [*] Missing return type annotation for public function `error_typed_self` | 41 | pass 42 | @@ -39,6 +59,14 @@ ignore_fully_untyped.py:43:9: ANN201 Missing return type annotation for public f | ^^^^^^^^^^^^^^^^ ANN201 44 | pass | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +40 40 | def ok_untyped_method(self): +41 41 | pass +42 42 | +43 |- def error_typed_self(self: X): + 43 |+ def error_typed_self(self: X) -> None: +44 44 | pass diff --git a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__mypy_init_return.snap b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__mypy_init_return.snap index 2ce0c94524eb4..f25fc530bc8cf 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__mypy_init_return.snap +++ b/crates/ruff_linter/src/rules/flake8_annotations/snapshots/ruff_linter__rules__flake8_annotations__tests__mypy_init_return.snap @@ -41,14 +41,24 @@ mypy_init_return.py:11:9: ANN204 [*] Missing return type annotation for special 13 13 | 14 14 | -mypy_init_return.py:40:5: ANN202 Missing return type annotation for private function `__init__` +mypy_init_return.py:40:5: ANN202 [*] Missing return type annotation for private function `__init__` | 39 | # Error 40 | def __init__(self, foo: int): | ^^^^^^^^ ANN202 41 | ... | - = help: Add return type annotation + = help: Add return type annotation: `None` + +ℹ Unsafe fix +37 37 | +38 38 | +39 39 | # Error +40 |-def __init__(self, foo: int): + 40 |+def __init__(self, foo: int) -> None: +41 41 | ... +42 42 | +43 43 | mypy_init_return.py:47:9: ANN204 [*] Missing return type annotation for special method `__init__` | diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 5d065fa30fc11..cca590903244b 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -911,6 +911,180 @@ where } } +/// Returns `true` if the function has an implicit return. +pub fn implicit_return(function: &ast::StmtFunctionDef) -> bool { + /// Returns `true` if the body may break via a `break` statement. + fn sometimes_breaks(stmts: &[Stmt]) -> bool { + for stmt in stmts { + match stmt { + Stmt::For(ast::StmtFor { body, orelse, .. }) => { + if returns(body) { + return false; + } + if sometimes_breaks(orelse) { + return true; + } + } + Stmt::While(ast::StmtWhile { body, orelse, .. }) => { + if returns(body) { + return false; + } + if sometimes_breaks(orelse) { + return true; + } + } + Stmt::If(ast::StmtIf { + body, + elif_else_clauses, + .. + }) => { + if std::iter::once(body) + .chain(elif_else_clauses.iter().map(|clause| &clause.body)) + .any(|body| sometimes_breaks(body)) + { + return true; + } + } + Stmt::Match(ast::StmtMatch { cases, .. }) => { + if cases.iter().any(|case| sometimes_breaks(&case.body)) { + return true; + } + } + Stmt::Try(ast::StmtTry { + body, + handlers, + orelse, + finalbody, + .. + }) => { + if sometimes_breaks(body) + || handlers.iter().any(|handler| { + let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { + body, + .. + }) = handler; + sometimes_breaks(body) + }) + || sometimes_breaks(orelse) + || sometimes_breaks(finalbody) + { + return true; + } + } + Stmt::With(ast::StmtWith { body, .. }) => { + if sometimes_breaks(body) { + return true; + } + } + Stmt::Break(_) => return true, + Stmt::Return(_) => return false, + Stmt::Raise(_) => return false, + _ => {} + } + } + false + } + + /// Returns `true` if the body may break via a `break` statement. + fn always_breaks(stmts: &[Stmt]) -> bool { + for stmt in stmts { + match stmt { + Stmt::Break(_) => return true, + Stmt::Return(_) => return false, + Stmt::Raise(_) => return false, + _ => {} + } + } + false + } + + /// Returns `true` if the body contains a branch that ends without an explicit `return` or + /// `raise` statement. + fn returns(stmts: &[Stmt]) -> bool { + for stmt in stmts.iter().rev() { + match stmt { + Stmt::For(ast::StmtFor { body, orelse, .. }) => { + if always_breaks(body) { + return false; + } + if returns(body) { + return true; + } + if returns(orelse) && !sometimes_breaks(body) { + return true; + } + } + Stmt::While(ast::StmtWhile { body, orelse, .. }) => { + if always_breaks(body) { + return false; + } + if returns(body) { + return true; + } + if returns(orelse) && !sometimes_breaks(body) { + return true; + } + } + Stmt::If(ast::StmtIf { + body, + elif_else_clauses, + .. + }) => { + if elif_else_clauses.iter().any(|clause| clause.test.is_none()) + && std::iter::once(body) + .chain(elif_else_clauses.iter().map(|clause| &clause.body)) + .all(|body| returns(body)) + { + return true; + } + } + Stmt::Match(ast::StmtMatch { cases, .. }) => { + // Note: we assume the `match` is exhaustive. + if cases.iter().all(|case| returns(&case.body)) { + return true; + } + } + Stmt::Try(ast::StmtTry { + body, + handlers, + orelse, + finalbody, + .. + }) => { + // If the `finally` block returns, the `try` block must also return. + if returns(finalbody) { + return true; + } + + // If the `body` or the `else` block returns, the `try` block must also return. + if (returns(body) || returns(orelse)) + && handlers.iter().all(|handler| { + let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { + body, + .. + }) = handler; + returns(body) + }) + { + return true; + } + } + Stmt::With(ast::StmtWith { body, .. }) => { + if returns(body) { + return true; + } + } + Stmt::Return(_) => return true, + Stmt::Raise(_) => return true, + _ => {} + } + } + false + } + + !returns(&function.body) +} + /// A [`StatementVisitor`] that collects all `raise` statements in a function or method. #[derive(Default)] pub struct RaiseStatementVisitor<'a> {