From a3d873ef667dd5f1d5c77132d11f376574b29c73 Mon Sep 17 00:00:00 2001 From: Auguste Lalande Date: Thu, 2 Jan 2025 22:54:59 -0500 Subject: [PATCH] [`pylint`] Re-implement `unreachable` (`PLW0101`) (#10891) ## Summary This PR re-introduces the control-flow graph implementation which was first introduced in #5384, and then removed in #9463 due to not being feature complete. Mainly, it lacked the ability to process `try`-`except` blocks, along with some more minor bugs. Closes #8958 and #8959 and #14881. ## Overview of Changes I will now highlight the major changes implemented in this PR, in order of implementation. 1. Introduced a post-processing step in loop handling to find any `continue` or `break` statements within the loop body and redirect them appropriately. 2. Introduced a loop-continue block which is always placed at the end of loop blocks, and ensures proper looping regardless of the internal logic of the block. This resolves #8958. 3. Implemented `try` processing with the following logic (resolves #8959): 1. In the example below the cfg first encounters a conditional `ExceptionRaised` forking if an exception was (or will be) raised in the try block. This is not possible to know (except for trivial cases) so we assume both paths can be taken unconditionally. 2. Going down the `try` path the cfg goes `try`->`else`->`finally` unconditionally. 3. Going down the `except` path the cfg will meet several conditional `ExceptionCaught` which fork depending on the nature of the exception caught. Again there's no way to know which exceptions may be raised so both paths are assumed to be taken unconditionally. 4. If none of the exception blocks catch the exception then the cfg terminates by raising a new exception. 5. A post-processing step is also implemented to redirect any `raises` or `returns` within the blocks appropriately. ```python def func(): try: print("try") except Exception: print("Exception") except OtherException as e: print("OtherException") else: print("else") finally: print("finally") ``` ```mermaid flowchart TD start(("Start")) return(("End")) block0[["`*(empty)*`"]] block1["print(#quot;finally#quot;)\n"] block2["print(#quot;else#quot;)\n"] block3["print(#quot;try#quot;)\n"] block4[["Exception raised"]] block5["print(#quot;OtherException#quot;)\n"] block6["try: print(#quot;try#quot;) except Exception: print(#quot;Exception#quot;) except OtherException as e: print(#quot;OtherException#quot;) else: print(#quot;else#quot;) finally: print(#quot;finally#quot;)\n"] block7["print(#quot;Exception#quot;)\n"] block8["try: print(#quot;try#quot;) except Exception: print(#quot;Exception#quot;) except OtherException as e: print(#quot;OtherException#quot;) else: print(#quot;else#quot;) finally: print(#quot;finally#quot;)\n"] block9["try: print(#quot;try#quot;) except Exception: print(#quot;Exception#quot;) except OtherException as e: print(#quot;OtherException#quot;) else: print(#quot;else#quot;) finally: print(#quot;finally#quot;)\n"] start --> block9 block9 -- "Exception raised" --> block8 block9 -- "else" --> block3 block8 -- "Exception" --> block7 block8 -- "else" --> block6 block7 --> block1 block6 -- "OtherException" --> block5 block6 -- "else" --> block4 block5 --> block1 block4 --> return block3 --> block2 block2 --> block1 block1 --> block0 block0 --> return ``` 6. Implemented `with` processing with the following logic: 1. `with` statements have no conditional execution (apart from the hidden logic handling the enter and exit), so the block is assumed to execute unconditionally. 2. The one exception is that exceptions raised within the block may result in control flow resuming at the end of the block. Since it is not possible know if an exception will be raised, or if it will be handled by the context manager, we assume that execution always continues after `with` blocks even if the blocks contain `raise` or `return` statements. This is handled in a post-processing step. ## Test Plan Additional test fixtures and control-flow fixtures were added. --------- Co-authored-by: Micha Reiser Co-authored-by: dylwil3 --- Cargo.lock | 1 + crates/ruff_linter/Cargo.toml | 1 + .../fixtures/control-flow-graph/assert.py | 26 + .../test/fixtures/control-flow-graph/for.py | 58 +- .../test/fixtures/control-flow-graph/if.py | 22 + .../fixtures/control-flow-graph/simple.py | 11 + .../test/fixtures/control-flow-graph/try.py | 111 +- .../test/fixtures/control-flow-graph/while.py | 38 +- .../test/fixtures/pylint/unreachable.py | 263 ++++ .../src/checkers/ast/analyze/statement.rs | 5 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/pylint/mod.rs | 1 + .../ruff_linter/src/rules/pylint/rules/mod.rs | 2 + ...les__unreachable__tests__assert.py.md.snap | 244 ++++ ...__unreachable__tests__async-for.py.md.snap | 257 ++++ ..._rules__unreachable__tests__for.py.md.snap | 590 ++++++++ ...__rules__unreachable__tests__if.py.md.snap | 720 ++++++++++ ...ules__unreachable__tests__match.py.md.snap | 823 +++++++++++ ...ules__unreachable__tests__raise.py.md.snap | 43 + ...les__unreachable__tests__simple.py.md.snap | 188 +++ ..._rules__unreachable__tests__try.py.md.snap | 710 ++++++++++ ...ules__unreachable__tests__while.py.md.snap | 779 +++++++++++ .../src/rules/pylint/rules/unreachable.rs | 1217 +++++++++++++++++ ...pylint__tests__PLW0101_unreachable.py.snap | 270 ++++ ruff.schema.json | 1 + 25 files changed, 6357 insertions(+), 25 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__assert.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__async-for.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__for.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__if.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__match.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__raise.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__simple.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__while.py.md.snap create mode 100644 crates/ruff_linter/src/rules/pylint/rules/unreachable.rs create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0101_unreachable.py.snap diff --git a/Cargo.lock b/Cargo.lock index 9150269dea3cf..9ddadae59163e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2768,6 +2768,7 @@ dependencies = [ "regex", "ruff_cache", "ruff_diagnostics", + "ruff_index", "ruff_macros", "ruff_notebook", "ruff_python_ast", diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 2bc618771afc4..a2a113f30ef2c 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -15,6 +15,7 @@ license = { workspace = true } [dependencies] ruff_cache = { workspace = true } ruff_diagnostics = { workspace = true, features = ["serde"] } +ruff_index = { workspace = true } ruff_notebook = { workspace = true } ruff_macros = { workspace = true } ruff_python_ast = { workspace = true, features = ["serde", "cache"] } diff --git a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/assert.py b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/assert.py index bfb3ab9030e90..b2cea94541581 100644 --- a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/assert.py +++ b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/assert.py @@ -9,3 +9,29 @@ def func(): def func(): assert False, "oops" + +def func(): + y = 2 + assert y == 2 + assert y > 1 + assert y < 3 + +def func(): + for i in range(3): + assert i < x + +def func(): + for j in range(3): + x = 2 + else: + assert False + return 1 + +def func(): + for j in range(3): + if j == 2: + print('yay') + break + else: + assert False + return 1 diff --git a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/for.py b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/for.py index 9aef74d5d027f..80d0af4eae2c7 100644 --- a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/for.py +++ b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/for.py @@ -40,8 +40,6 @@ def func(): if True: break -# TODO(charlie): The `pass` here does not get properly redirected to the top of the -# loop, unlike below. def func(): for i in range(5): pass @@ -54,3 +52,59 @@ def func(): else: return 1 x = 1 + +def func(): + for i in range(5): + pass + else: + pass + +def func(): + for i in range(3): + if i == 2: + assert i is not None + break + else: + raise Exception() + x = 0 + +def func(): + for i in range(13): + for i in range(12): + x = 2 + if True: + break + + x = 3 + if True: + break + + print('hello') + + +def func(): + for i in range(13): + for i in range(12): + x = 2 + if True: + continue + + x = 3 + if True: + break + + print('hello') + + +def func(): + for i in range(13): + for i in range(12): + x = 2 + if True: + break + + x = 3 + if True: + continue + + print('hello') diff --git a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/if.py b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/if.py index 2b5fa420990ec..d505ef5857738 100644 --- a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/if.py +++ b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/if.py @@ -106,3 +106,25 @@ def func(self, obj: BytesRep) -> bytes: self.error(f"can't resolve buffer '{id}'") return buffer.data + +def func(x): + if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6 + +def func(): + if x: + return + else: + assert x + + print('pop') diff --git a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/simple.py b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/simple.py index d1f710149b627..477955ce04af1 100644 --- a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/simple.py +++ b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/simple.py @@ -21,3 +21,14 @@ def func(): i = 0 i += 2 return i + +def func(): + with x: + i = 0 + i = 1 + +def func(): + with x: + i = 0 + return 1 + i = 1 diff --git a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/try.py b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/try.py index e9f109dfd7bf6..24c43ef1eef8c 100644 --- a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/try.py +++ b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/try.py @@ -1,41 +1,118 @@ def func(): try: - ... + print("try") except Exception: - ... + print("Exception") except OtherException as e: - ... + print("OtherException") else: - ... + print("else") finally: - ... + print("finally") def func(): try: - ... - except Exception: - ... + print("try") + except: + print("Exception") + +def func(): + try: + print("try") + except: + print("Exception") + except OtherException as e: + print("OtherException") def func(): try: - ... + print("try") except Exception: - ... + print("Exception") except OtherException as e: - ... + print("OtherException") def func(): try: - ... + print("try") except Exception: - ... + print("Exception") except OtherException as e: - ... + print("OtherException") else: - ... + print("else") + +def func(): + try: + print("try") + finally: + print("finally") def func(): try: - ... + return 0 + except: + return 1 + finally: + return 2 + +def func(): + try: + raise Exception() + except: + print("reached") + +def func(): + try: + assert False + print("unreachable") + except: + print("reached") + +def func(): + try: + raise Exception() + finally: + print('reached') + return 2 + +def func(): + try: + assert False + print("unreachable") + finally: + print("reached") + +# Test case from ibis caused overflow +def func(): + try: + if catalog is not None: + try: + x = 0 + except PySparkParseException: + x = 1 + try: + x = 2 + except PySparkParseException: + x = 3 + x = 8 + finally: + if catalog is not None: + try: + x = 4 + except PySparkParseException: + x = 5 + try: + x = 6 + except PySparkParseException: + x = 7 + + +def func(): + try: + assert False + except ex: + raise ex + finally: - ... + raise Exception("other") diff --git a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/while.py b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/while.py index 6a4174358bdba..11d3f6164c364 100644 --- a/crates/ruff_linter/resources/test/fixtures/control-flow-graph/while.py +++ b/crates/ruff_linter/resources/test/fixtures/control-flow-graph/while.py @@ -99,12 +99,39 @@ def func(): if True: break -''' -TODO: because `try` statements aren't handled this triggers a false positive as -the last statement is reached, but the rules thinks it isn't (it doesn't -see/process the break statement). +def func(): + while True: + x = 0 + x = 1 + break + x = 2 + x = 3 + +def func(): + while True: + x = 0 + x = 1 + continue + x = 2 + x = 3 + +def func(): + while True: + x = 0 + x = 1 + return + x = 2 + x = 3 + +def func(): + while True: + x = 0 + x = 1 + raise Exception + x = 2 + x = 3 -# Test case found in the Bokeh repository that trigger a false positive. +# Test case found in the Bokeh repository that triggered a false positive. def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None: self.stop_serving = False while True: @@ -118,4 +145,3 @@ def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None: port += 1 self.thread = threading.Thread(target=self._run_web_server) -''' diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py b/crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py new file mode 100644 index 0000000000000..a0079060f6cb2 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py @@ -0,0 +1,263 @@ +def after_return(): + return "reachable" + return "unreachable" + +async def also_works_on_async_functions(): + return "reachable" + return "unreachable" + +def if_always_true(): + if True: + return "reachable" + return "unreachable" + +def if_always_false(): + if False: + return "unreachable" + return "reachable" + +def if_elif_always_false(): + if False: + return "unreachable" + elif False: + return "also unreachable" + return "reachable" + +def if_elif_always_true(): + if False: + return "unreachable" + elif True: + return "reachable" + return "also unreachable" + +def ends_with_if(): + if False: + return "unreachable" + else: + return "reachable" + +def infinite_loop(): + while True: + continue + return "unreachable" + +''' TODO: we could determine these, but we don't yet. +def for_range_return(): + for i in range(10): + if i == 5: + return "reachable" + return "unreachable" + +def for_range_else(): + for i in range(111): + if i == 5: + return "reachable" + else: + return "unreachable" + return "also unreachable" + +def for_range_break(): + for i in range(13): + return "reachable" + return "unreachable" + +def for_range_if_break(): + for i in range(1110): + if True: + return "reachable" + return "unreachable" +''' + +def match_wildcard(status): + match status: + case _: + return "reachable" + return "unreachable" + +def match_case_and_wildcard(status): + match status: + case 1: + return "reachable" + case _: + return "reachable" + return "unreachable" + +def raise_exception(): + raise Exception + return "unreachable" + +def while_false(): + while False: + return "unreachable" + return "reachable" + +def while_false_else(): + while False: + return "unreachable" + else: + return "reachable" + +def while_false_else_return(): + while False: + return "unreachable" + else: + return "reachable" + return "also unreachable" + +def while_true(): + while True: + return "reachable" + return "unreachable" + +def while_true_else(): + while True: + return "reachable" + else: + return "unreachable" + +def while_true_else_return(): + while True: + return "reachable" + else: + return "unreachable" + return "also unreachable" + +def while_false_var_i(): + i = 0 + while False: + i += 1 + return i + +def while_true_var_i(): + i = 0 + while True: + i += 1 + return i + +def while_infinite(): + while True: + pass + return "unreachable" + +def while_if_true(): + while True: + if True: + return "reachable" + return "unreachable" + +def while_break(): + while True: + print("reachable") + break + print("unreachable") + return "reachable" + +# Test case found in the Bokeh repository that triggered a false positive. +def bokeh1(self, obj: BytesRep) -> bytes: + data = obj["data"] + + if isinstance(data, str): + return base64.b64decode(data) + elif isinstance(data, Buffer): + buffer = data + else: + id = data["id"] + + if id in self._buffers: + buffer = self._buffers[id] + else: + self.error(f"can't resolve buffer '{id}'") + + return buffer.data + +# Test case found in the Bokeh repository that triggered a false positive. +def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None: + self.stop_serving = False + while True: + try: + self.server = HTTPServer((host, port), HtmlOnlyHandler) + self.host = host + self.port = port + break + except OSError: + log.debug(f"port {port} is in use, trying to next one") + port += 1 + + self.thread = threading.Thread(target=self._run_web_server) + +# Test case found in the pandas repository that triggered a false positive. +def _check_basic_constructor(self, empty): + # mat: 2d matrix with shape (3, 2) to input. empty - makes sized + # objects + mat = empty((2, 3), dtype=float) + # 2-D input + frame = DataFrame(mat, columns=["A", "B", "C"], index=[1, 2]) + + assert len(frame.index) == 2 + assert len(frame.columns) == 3 + + # 1-D input + frame = DataFrame(empty((3,)), columns=["A"], index=[1, 2, 3]) + assert len(frame.index) == 3 + assert len(frame.columns) == 1 + + if empty is not np.ones: + msg = r"Cannot convert non-finite values \(NA or inf\) to integer" + with pytest.raises(IntCastingNaNError, match=msg): + DataFrame(mat, columns=["A", "B", "C"], index=[1, 2], dtype=np.int64) + return + else: + frame = DataFrame( + mat, columns=["A", "B", "C"], index=[1, 2], dtype=np.int64 + ) + assert frame.values.dtype == np.int64 + + # wrong size axis labels + msg = r"Shape of passed values is \(2, 3\), indices imply \(1, 3\)" + with pytest.raises(ValueError, match=msg): + DataFrame(mat, columns=["A", "B", "C"], index=[1]) + msg = r"Shape of passed values is \(2, 3\), indices imply \(2, 2\)" + with pytest.raises(ValueError, match=msg): + DataFrame(mat, columns=["A", "B"], index=[1, 2]) + + # higher dim raise exception + with pytest.raises(ValueError, match="Must pass 2-d input"): + DataFrame(empty((3, 3, 3)), columns=["A", "B", "C"], index=[1]) + + # automatic labeling + frame = DataFrame(mat) + tm.assert_index_equal(frame.index, Index(range(2)), exact=True) + tm.assert_index_equal(frame.columns, Index(range(3)), exact=True) + + frame = DataFrame(mat, index=[1, 2]) + tm.assert_index_equal(frame.columns, Index(range(3)), exact=True) + + frame = DataFrame(mat, columns=["A", "B", "C"]) + tm.assert_index_equal(frame.index, Index(range(2)), exact=True) + + # 0-length axis + frame = DataFrame(empty((0, 3))) + assert len(frame.index) == 0 + + frame = DataFrame(empty((3, 0))) + assert len(frame.columns) == 0 + + +def after_return(): + return "reachable" + print("unreachable") + print("unreachable") + print("unreachable") + print("unreachable") + print("unreachable") + + +def check_if_url_exists(url: str) -> bool: # type: ignore[return] + return True # uncomment to check URLs + response = requests.head(url, allow_redirects=True) + if response.status_code == 200: + return True + if response.status_code == 404: + return False + console.print(f"[red]Unexpected error received: {response.status_code}[/]") + response.raise_for_status() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 7d9a92a6f1faa..a780c4e3ab5a1 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -366,6 +366,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::AsyncFunctionWithTimeout) { flake8_async::rules::async_function_with_timeout(checker, function_def); } + if checker.enabled(Rule::UnreachableCode) { + checker + .diagnostics + .extend(pylint::rules::in_function(name, body)); + } if checker.enabled(Rule::ReimplementedOperator) { refurb::rules::reimplemented_operator(checker, &function_def.into()); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index e2e9d67bbd8ed..55453c58210e8 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -268,6 +268,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "R6104") => (RuleGroup::Preview, rules::pylint::rules::NonAugmentedAssignment), (Pylint, "R6201") => (RuleGroup::Preview, rules::pylint::rules::LiteralMembership), (Pylint, "R6301") => (RuleGroup::Preview, rules::pylint::rules::NoSelfUse), + (Pylint, "W0101") => (RuleGroup::Preview, rules::pylint::rules::UnreachableCode), (Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda), (Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison), (Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 9a374357b8d46..784f3f89954a9 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -161,6 +161,7 @@ mod tests { #[test_case(Rule::UselessImportAlias, Path::new("import_aliasing.py"))] #[test_case(Rule::UselessReturn, Path::new("useless_return.py"))] #[test_case(Rule::UselessWithLock, Path::new("useless_with_lock.py"))] + #[test_case(Rule::UnreachableCode, Path::new("unreachable.py"))] #[test_case( Rule::YieldFromInAsyncFunction, Path::new("yield_from_in_async_function.py") diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index 5d63275f03a43..8d019d0887a48 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -95,6 +95,7 @@ pub(crate) use unnecessary_direct_lambda_call::*; pub(crate) use unnecessary_dunder_call::*; pub(crate) use unnecessary_lambda::*; pub(crate) use unnecessary_list_index_lookup::*; +pub(crate) use unreachable::*; pub(crate) use unspecified_encoding::*; pub(crate) use useless_else_on_loop::*; pub(crate) use useless_exception_statement::*; @@ -201,6 +202,7 @@ mod unnecessary_direct_lambda_call; mod unnecessary_dunder_call; mod unnecessary_lambda; mod unnecessary_list_index_lookup; +mod unreachable; mod unspecified_encoding; mod useless_else_on_loop; mod useless_exception_statement; diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__assert.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__assert.py.md.snap new file mode 100644 index 0000000000000..8c64a425ab804 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__assert.py.md.snap @@ -0,0 +1,244 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + assert True +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["assert True\n"] + + start --> block2 + block2 -- "True" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + assert False +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["assert False\n"] + + start --> block2 + block2 -- "False" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + assert True, "oops" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["assert True, #quot;oops#quot;\n"] + + start --> block2 + block2 -- "True" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + assert False, "oops" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["assert False, #quot;oops#quot;\n"] + + start --> block2 + block2 -- "False" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + y = 2 + assert y == 2 + assert y > 1 + assert y < 3 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["assert y < 3\n"] + block3[["Exception raised"]] + block4["assert y > 1\n"] + block5[["Exception raised"]] + block6["y = 2\nassert y == 2\n"] + + start --> block6 + block6 -- "y == 2" --> block4 + block6 -- "else" --> block5 + block5 --> return + block4 -- "y > 1" --> block2 + block4 -- "else" --> block3 + block3 --> return + block2 -- "y < 3" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + for i in range(3): + assert i < x +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2[["Exception raised"]] + block3["assert i < x\n"] + block4["for i in range(3): + assert i < x\n"] + + start --> block4 + block4 -- "range(3)" --> block3 + block4 -- "else" --> block0 + block3 -- "i < x" --> block1 + block3 -- "else" --> block2 + block2 --> return + block1 --> block4 + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + for j in range(3): + x = 2 + else: + assert False + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 1\n"] + block1[["Exception raised"]] + block2["assert False\n"] + block3[["Loop continue"]] + block4["x = 2\n"] + block5["for j in range(3): + x = 2 + else: + assert False\n"] + + start --> block5 + block5 -- "range(3)" --> block4 + block5 -- "else" --> block2 + block4 --> block3 + block3 --> block5 + block2 -- "False" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + for j in range(3): + if j == 2: + print('yay') + break + else: + assert False + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 1\n"] + block1[["Exception raised"]] + block2["assert False\n"] + block3[["Loop continue"]] + block4["print('yay')\nbreak\n"] + block5["if j == 2: + print('yay') + break\n"] + block6["for j in range(3): + if j == 2: + print('yay') + break + else: + assert False\n"] + + start --> block6 + block6 -- "range(3)" --> block5 + block6 -- "else" --> block2 + block5 -- "j == 2" --> block4 + block5 -- "else" --> block3 + block4 --> block0 + block3 --> block6 + block2 -- "False" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__async-for.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__async-for.py.md.snap new file mode 100644 index 0000000000000..327495f2db68d --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__async-for.py.md.snap @@ -0,0 +1,257 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + async for i in range(5): + print(i) +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["print(i)\n"] + block3["async for i in range(5): + print(i)\n"] + + start --> block3 + block3 -- "range(5)" --> block2 + block3 -- "else" --> block0 + block2 --> block1 + block1 --> block3 + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + async for i in range(20): + print(i) + else: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2[["Loop continue"]] + block3["print(i)\n"] + block4["async for i in range(20): + print(i) + else: + return 0\n"] + + start --> block4 + block4 -- "range(20)" --> block3 + block4 -- "else" --> block1 + block3 --> block2 + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + async for i in range(10): + if i == 5: + return 1 + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 0\n"] + block1[["Loop continue"]] + block2["return 1\n"] + block3["if i == 5: + return 1\n"] + block4["async for i in range(10): + if i == 5: + return 1\n"] + + start --> block4 + block4 -- "range(10)" --> block3 + block4 -- "else" --> block0 + block3 -- "i == 5" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> block4 + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + async for i in range(111): + if i == 5: + return 1 + else: + return 0 + return 2 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 2\n"] + block1["return 0\n"] + block2[["Loop continue"]] + block3["return 1\n"] + block4["if i == 5: + return 1\n"] + block5["async for i in range(111): + if i == 5: + return 1 + else: + return 0\n"] + + start --> block5 + block5 -- "range(111)" --> block4 + block5 -- "else" --> block1 + block4 -- "i == 5" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> block5 + block1 --> return + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + async for i in range(12): + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["async for i in range(12): + continue\n"] + + start --> block3 + block3 -- "range(12)" --> block2 + block3 -- "else" --> block0 + block2 --> block3 + block1 --> block3 + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + async for i in range(1110): + if True: + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["if True: + continue\n"] + block4["async for i in range(1110): + if True: + continue\n"] + + start --> block4 + block4 -- "range(1110)" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> block4 + block1 --> block4 + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + async for i in range(13): + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["async for i in range(13): + break\n"] + + start --> block3 + block3 -- "range(13)" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> block3 + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + async for i in range(1110): + if True: + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["if True: + break\n"] + block4["async for i in range(1110): + if True: + break\n"] + + start --> block4 + block4 -- "range(1110)" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> block4 + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__for.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__for.py.md.snap new file mode 100644 index 0000000000000..71cd265b1569d --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__for.py.md.snap @@ -0,0 +1,590 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + for i in range(5): + print(i) +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["print(i)\n"] + block3["for i in range(5): + print(i)\n"] + + start --> block3 + block3 -- "range(5)" --> block2 + block3 -- "else" --> block0 + block2 --> block1 + block1 --> block3 + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + for i in range(20): + print(i) + else: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2[["Loop continue"]] + block3["print(i)\n"] + block4["for i in range(20): + print(i) + else: + return 0\n"] + + start --> block4 + block4 -- "range(20)" --> block3 + block4 -- "else" --> block1 + block3 --> block2 + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + for i in range(10): + if i == 5: + return 1 + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 0\n"] + block1[["Loop continue"]] + block2["return 1\n"] + block3["if i == 5: + return 1\n"] + block4["for i in range(10): + if i == 5: + return 1\n"] + + start --> block4 + block4 -- "range(10)" --> block3 + block4 -- "else" --> block0 + block3 -- "i == 5" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> block4 + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + for i in range(111): + if i == 5: + return 1 + else: + return 0 + return 2 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 2\n"] + block1["return 0\n"] + block2[["Loop continue"]] + block3["return 1\n"] + block4["if i == 5: + return 1\n"] + block5["for i in range(111): + if i == 5: + return 1 + else: + return 0\n"] + + start --> block5 + block5 -- "range(111)" --> block4 + block5 -- "else" --> block1 + block4 -- "i == 5" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> block5 + block1 --> return + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + for i in range(12): + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["for i in range(12): + continue\n"] + + start --> block3 + block3 -- "range(12)" --> block2 + block3 -- "else" --> block0 + block2 --> block3 + block1 --> block3 + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + for i in range(1110): + if True: + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["if True: + continue\n"] + block4["for i in range(1110): + if True: + continue\n"] + + start --> block4 + block4 -- "range(1110)" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> block4 + block1 --> block4 + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + for i in range(13): + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["for i in range(13): + break\n"] + + start --> block3 + block3 -- "range(13)" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> block3 + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + for i in range(1110): + if True: + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["if True: + break\n"] + block4["for i in range(1110): + if True: + break\n"] + + start --> block4 + block4 -- "range(1110)" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> block4 + block0 --> return +``` + +## Function 8 +### Source +```python +def func(): + for i in range(5): + pass + else: + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2[["Loop continue"]] + block3["pass\n"] + block4["for i in range(5): + pass + else: + return 1\n"] + + start --> block4 + block4 -- "range(5)" --> block3 + block4 -- "else" --> block1 + block3 --> block2 + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 9 +### Source +```python +def func(): + for i in range(5): + pass + else: + return 1 + x = 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["x = 1\n"] + block1["return 1\n"] + block2[["Loop continue"]] + block3["pass\n"] + block4["for i in range(5): + pass + else: + return 1\n"] + + start --> block4 + block4 -- "range(5)" --> block3 + block4 -- "else" --> block1 + block3 --> block2 + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 10 +### Source +```python +def func(): + for i in range(5): + pass + else: + pass +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["pass\n"] + block2[["Loop continue"]] + block3["pass\n"] + block4["for i in range(5): + pass + else: + pass\n"] + + start --> block4 + block4 -- "range(5)" --> block3 + block4 -- "else" --> block1 + block3 --> block2 + block2 --> block4 + block1 --> block0 + block0 --> return +``` + +## Function 11 +### Source +```python +def func(): + for i in range(3): + if i == 2: + assert i is not None + break + else: + raise Exception() + x = 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["x = 0\n"] + block1[["Exception raised"]] + block2["raise Exception()\n"] + block3[["Loop continue"]] + block4["break\n"] + block5[["Exception raised"]] + block6["assert i is not None\n"] + block7["if i == 2: + assert i is not None + break\n"] + block8["for i in range(3): + if i == 2: + assert i is not None + break + else: + raise Exception()\n"] + + start --> block8 + block8 -- "range(3)" --> block7 + block8 -- "else" --> block2 + block7 -- "i == 2" --> block6 + block7 -- "else" --> block3 + block6 -- "i is not None" --> block4 + block6 -- "else" --> block5 + block5 --> return + block4 --> block0 + block3 --> block8 + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 12 +### Source +```python +def func(): + for i in range(13): + for i in range(12): + x = 2 + if True: + break + + x = 3 + if True: + break + + print('hello') +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["print('hello')\n"] + block1[["Loop continue"]] + block2["break\n"] + block3["x = 3\nif True: + break\n"] + block4[["Loop continue"]] + block5["break\n"] + block6["x = 2\nif True: + break\n"] + block7["for i in range(12): + x = 2 + if True: + break\n"] + block8["for i in range(13): + for i in range(12): + x = 2 + if True: + break + + x = 3 + if True: + break\n"] + + start --> block8 + block8 -- "range(13)" --> block7 + block8 -- "else" --> block0 + block7 -- "range(12)" --> block6 + block7 -- "else" --> block3 + block6 -- "True" --> block5 + block6 -- "else" --> block4 + block5 --> block3 + block4 --> block7 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> block0 + block1 --> block8 + block0 --> return +``` + +## Function 13 +### Source +```python +def func(): + for i in range(13): + for i in range(12): + x = 2 + if True: + continue + + x = 3 + if True: + break + + print('hello') +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["print('hello')\n"] + block1[["Loop continue"]] + block2["break\n"] + block3["x = 3\nif True: + break\n"] + block4[["Loop continue"]] + block5["continue\n"] + block6["x = 2\nif True: + continue\n"] + block7["for i in range(12): + x = 2 + if True: + continue\n"] + block8["for i in range(13): + for i in range(12): + x = 2 + if True: + continue + + x = 3 + if True: + break\n"] + + start --> block8 + block8 -- "range(13)" --> block7 + block8 -- "else" --> block0 + block7 -- "range(12)" --> block6 + block7 -- "else" --> block3 + block6 -- "True" --> block5 + block6 -- "else" --> block4 + block5 --> block7 + block4 --> block7 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> block0 + block1 --> block8 + block0 --> return +``` + +## Function 14 +### Source +```python +def func(): + for i in range(13): + for i in range(12): + x = 2 + if True: + break + + x = 3 + if True: + continue + + print('hello') +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["print('hello')\n"] + block1[["Loop continue"]] + block2["continue\n"] + block3["x = 3\nif True: + continue\n"] + block4[["Loop continue"]] + block5["break\n"] + block6["x = 2\nif True: + break\n"] + block7["for i in range(12): + x = 2 + if True: + break\n"] + block8["for i in range(13): + for i in range(12): + x = 2 + if True: + break + + x = 3 + if True: + continue\n"] + + start --> block8 + block8 -- "range(13)" --> block7 + block8 -- "else" --> block0 + block7 -- "range(12)" --> block6 + block7 -- "else" --> block3 + block6 -- "True" --> block5 + block6 -- "else" --> block4 + block5 --> block3 + block4 --> block7 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> block8 + block1 --> block8 + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__if.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__if.py.md.snap new file mode 100644 index 0000000000000..7f158e62c3307 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__if.py.md.snap @@ -0,0 +1,720 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + if False: + return 0 + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 1\n"] + block1["return 0\n"] + block2["if False: + return 0\n"] + + start --> block2 + block2 -- "False" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + if True: + return 1 + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 0\n"] + block1["return 1\n"] + block2["if True: + return 1\n"] + + start --> block2 + block2 -- "True" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + if False: + return 0 + else: + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2["return 1\n"] + block3["if False: + return 0 + else: + return 1\n"] + + start --> block3 + block3 -- "False" --> block1 + block3 -- "else" --> block2 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + if True: + return 1 + else: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2["return 0\n"] + block3["if True: + return 1 + else: + return 0\n"] + + start --> block3 + block3 -- "True" --> block1 + block3 -- "else" --> block2 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + if False: + return 0 + else: + return 1 + return "unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;unreachable#quot;\n"] + block1["return 0\n"] + block2["return 1\n"] + block3["if False: + return 0 + else: + return 1\n"] + + start --> block3 + block3 -- "False" --> block1 + block3 -- "else" --> block2 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + if True: + return 1 + else: + return 0 + return "unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;unreachable#quot;\n"] + block1["return 1\n"] + block2["return 0\n"] + block3["if True: + return 1 + else: + return 0\n"] + + start --> block3 + block3 -- "True" --> block1 + block3 -- "else" --> block2 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + if True: + if True: + return 1 + return 2 + else: + return 3 + return "unreachable2" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;unreachable2#quot;\n"] + block1["return 2\n"] + block2["return 1\n"] + block3["if True: + return 1\n"] + block4["return 3\n"] + block5["if True: + if True: + return 1 + return 2 + else: + return 3\n"] + + start --> block5 + block5 -- "True" --> block3 + block5 -- "else" --> block4 + block4 --> return + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + if False: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2["if False: + return 0\n"] + + start --> block2 + block2 -- "False" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 8 +### Source +```python +def func(): + if True: + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2["if True: + return 1\n"] + + start --> block2 + block2 -- "True" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 9 +### Source +```python +def func(): + if True: + return 1 + elif False: + return 2 + else: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2["return 0\n"] + block3["return 2\n"] + block4["if True: + return 1 + elif False: + return 2 + else: + return 0\n"] + block5["if True: + return 1 + elif False: + return 2 + else: + return 0\n"] + + start --> block5 + block5 -- "True" --> block1 + block5 -- "else" --> block4 + block4 -- "False" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 10 +### Source +```python +def func(): + if False: + return 1 + elif True: + return 2 + else: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2["return 0\n"] + block3["return 2\n"] + block4["if False: + return 1 + elif True: + return 2 + else: + return 0\n"] + block5["if False: + return 1 + elif True: + return 2 + else: + return 0\n"] + + start --> block5 + block5 -- "False" --> block1 + block5 -- "else" --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 11 +### Source +```python +def func(): + if True: + if False: + return 0 + elif True: + return 1 + else: + return 2 + return 3 + elif True: + return 4 + else: + return 5 + return 6 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 6\n"] + block1["return 3\n"] + block2["return 0\n"] + block3["return 2\n"] + block4["return 1\n"] + block5["if False: + return 0 + elif True: + return 1 + else: + return 2\n"] + block6["if False: + return 0 + elif True: + return 1 + else: + return 2\n"] + block7["return 5\n"] + block8["return 4\n"] + block9["if True: + if False: + return 0 + elif True: + return 1 + else: + return 2 + return 3 + elif True: + return 4 + else: + return 5\n"] + block10["if True: + if False: + return 0 + elif True: + return 1 + else: + return 2 + return 3 + elif True: + return 4 + else: + return 5\n"] + + start --> block10 + block10 -- "True" --> block6 + block10 -- "else" --> block9 + block9 -- "True" --> block8 + block9 -- "else" --> block7 + block8 --> return + block7 --> return + block6 -- "False" --> block2 + block6 -- "else" --> block5 + block5 -- "True" --> block4 + block5 -- "else" --> block3 + block4 --> return + block3 --> return + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 12 +### Source +```python +def func(): + if False: + return "unreached" + elif False: + return "also unreached" + return "reached" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;reached#quot;\n"] + block1["return #quot;unreached#quot;\n"] + block2["return #quot;also unreached#quot;\n"] + block3["if False: + return #quot;unreached#quot; + elif False: + return #quot;also unreached#quot;\n"] + block4["if False: + return #quot;unreached#quot; + elif False: + return #quot;also unreached#quot;\n"] + + start --> block4 + block4 -- "False" --> block1 + block4 -- "else" --> block3 + block3 -- "False" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 13 +### Source +```python +def func(self, obj: BytesRep) -> bytes: + data = obj["data"] + + if isinstance(data, str): + return base64.b64decode(data) + elif isinstance(data, Buffer): + buffer = data + else: + id = data["id"] + + if id in self._buffers: + buffer = self._buffers[id] + else: + self.error(f"can't resolve buffer '{id}'") + + return buffer.data +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return buffer.data\n"] + block1["return base64.b64decode(data)\n"] + block2["buffer = self._buffers[id]\n"] + block3["self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] + block4["id = data[#quot;id#quot;]\nif id in self._buffers: + buffer = self._buffers[id] + else: + self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] + block5["buffer = data\n"] + block6["if isinstance(data, str): + return base64.b64decode(data) + elif isinstance(data, Buffer): + buffer = data + else: + id = data[#quot;id#quot;] + + if id in self._buffers: + buffer = self._buffers[id] + else: + self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] + block7["data = obj[#quot;data#quot;]\nif isinstance(data, str): + return base64.b64decode(data) + elif isinstance(data, Buffer): + buffer = data + else: + id = data[#quot;id#quot;] + + if id in self._buffers: + buffer = self._buffers[id] + else: + self.error(f#quot;can't resolve buffer '{id}'#quot;)\n"] + + start --> block7 + block7 -- "isinstance(data, str)" --> block1 + block7 -- "else" --> block6 + block6 -- "isinstance(data, Buffer)" --> block5 + block6 -- "else" --> block4 + block5 --> block0 + block4 -- "id in self._buffers" --> block2 + block4 -- "else" --> block3 + block3 --> block0 + block2 --> block0 + block1 --> return + block0 --> return +``` + +## Function 14 +### Source +```python +def func(x): + if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2["return 6\n"] + block3["if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6\n"] + block4["return 5\n"] + block5["if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6\n"] + block6["return 4\n"] + block7["if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6\n"] + block8["return 3\n"] + block9["if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6\n"] + block10["return 2\n"] + block11["if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6\n"] + block12["if x == 1: + return 1 + elif False: + return 2 + elif x == 3: + return 3 + elif True: + return 4 + elif x == 5: + return 5 + elif x == 6: + return 6\n"] + + start --> block12 + block12 -- "x == 1" --> block1 + block12 -- "else" --> block11 + block11 -- "False" --> block10 + block11 -- "else" --> block9 + block10 --> return + block9 -- "x == 3" --> block8 + block9 -- "else" --> block7 + block8 --> return + block7 -- "True" --> block6 + block7 -- "else" --> block5 + block6 --> return + block5 -- "x == 5" --> block4 + block5 -- "else" --> block3 + block4 --> return + block3 -- "x == 6" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 15 +### Source +```python +def func(): + if x: + return + else: + assert x + + print('pop') +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["print('pop')\n"] + block1["return\n"] + block2[["Exception raised"]] + block3["assert x\n"] + block4["if x: + return + else: + assert x\n"] + + start --> block4 + block4 -- "x" --> block1 + block4 -- "else" --> block3 + block3 -- "x" --> block0 + block3 -- "else" --> block2 + block2 --> return + block1 --> return + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__match.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__match.py.md.snap new file mode 100644 index 0000000000000..a91f351cd0fb8 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__match.py.md.snap @@ -0,0 +1,823 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(status): + match status: + case _: + return 0 + return "unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;unreachable#quot;\n"] + block1["return 0\n"] + block2["match status: + case _: + return 0\n"] + + start --> block2 + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 1 +### Source +```python +def func(status): + match status: + case 1: + return 1 + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 0\n"] + block1["return 1\n"] + block2["match status: + case 1: + return 1\n"] + + start --> block2 + block2 -- "case 1" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 2 +### Source +```python +def func(status): + match status: + case 1: + return 1 + case _: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2["match status: + case 1: + return 1 + case _: + return 0\n"] + block3["return 1\n"] + block4["match status: + case 1: + return 1 + case _: + return 0\n"] + + start --> block4 + block4 -- "case 1" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 3 +### Source +```python +def func(status): + match status: + case 1 | 2 | 3: + return 5 + return 6 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 6\n"] + block1["return 5\n"] + block2["match status: + case 1 | 2 | 3: + return 5\n"] + + start --> block2 + block2 -- "case 1 | 2 | 3" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 4 +### Source +```python +def func(status): + match status: + case 1 | 2 | 3: + return 5 + case _: + return 10 + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 0\n"] + block1["return 10\n"] + block2["match status: + case 1 | 2 | 3: + return 5 + case _: + return 10\n"] + block3["return 5\n"] + block4["match status: + case 1 | 2 | 3: + return 5 + case _: + return 10\n"] + + start --> block4 + block4 -- "case 1 | 2 | 3" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 5 +### Source +```python +def func(status): + match status: + case 0: + return 0 + case 1: + return 1 + case 1: + return "1 again" + case _: + return 3 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 3\n"] + block2["match status: + case 0: + return 0 + case 1: + return 1 + case 1: + return #quot;1 again#quot; + case _: + return 3\n"] + block3["return #quot;1 again#quot;\n"] + block4["match status: + case 0: + return 0 + case 1: + return 1 + case 1: + return #quot;1 again#quot; + case _: + return 3\n"] + block5["return 1\n"] + block6["match status: + case 0: + return 0 + case 1: + return 1 + case 1: + return #quot;1 again#quot; + case _: + return 3\n"] + block7["return 0\n"] + block8["match status: + case 0: + return 0 + case 1: + return 1 + case 1: + return #quot;1 again#quot; + case _: + return 3\n"] + + start --> block8 + block8 -- "case 0" --> block7 + block8 -- "else" --> block6 + block7 --> return + block6 -- "case 1" --> block5 + block6 -- "else" --> block4 + block5 --> return + block4 -- "case 1" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 6 +### Source +```python +def func(status): + i = 0 + match status, i: + case _, _: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2["match status, i: + case _, _: + return 0\n"] + block3["i = 0\n"] + + start --> block3 + block3 --> block2 + block2 -- "case _, _" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 7 +### Source +```python +def func(status): + i = 0 + match status, i: + case _, 0: + return 0 + case _, 2: + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 0\n"] + block2["match status, i: + case _, 0: + return 0 + case _, 2: + return 0\n"] + block3["return 0\n"] + block4["match status, i: + case _, 0: + return 0 + case _, 2: + return 0\n"] + block5["i = 0\n"] + + start --> block5 + block5 --> block4 + block4 -- "case _, 0" --> block3 + block4 -- "else" --> block2 + block3 --> return + block2 -- "case _, 2" --> block1 + block2 -- "else" --> block0 + block1 --> return + block0 --> return +``` + +## Function 8 +### Source +```python +def func(point): + match point: + case (0, 0): + print("Origin") + case _: + raise ValueError("oops") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["raise ValueError(#quot;oops#quot;)\n"] + block3["match point: + case (0, 0): + print(#quot;Origin#quot;) + case _: + raise ValueError(#quot;oops#quot;)\n"] + block4["print(#quot;Origin#quot;)\n"] + block5["match point: + case (0, 0): + print(#quot;Origin#quot;) + case _: + raise ValueError(#quot;oops#quot;)\n"] + + start --> block5 + block5 -- "case (0, 0)" --> block4 + block5 -- "else" --> block3 + block4 --> block0 + block3 --> block2 + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 9 +### Source +```python +def func(point): + match point: + case (0, 0): + print("Origin") + case (0, y): + print(f"Y={y}") + case (x, 0): + print(f"X={x}") + case (x, y): + print(f"X={x}, Y={y}") + case _: + raise ValueError("Not a point") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["raise ValueError(#quot;Not a point#quot;)\n"] + block3["match point: + case (0, 0): + print(#quot;Origin#quot;) + case (0, y): + print(f#quot;Y={y}#quot;) + case (x, 0): + print(f#quot;X={x}#quot;) + case (x, y): + print(f#quot;X={x}, Y={y}#quot;) + case _: + raise ValueError(#quot;Not a point#quot;)\n"] + block4["print(f#quot;X={x}, Y={y}#quot;)\n"] + block5["match point: + case (0, 0): + print(#quot;Origin#quot;) + case (0, y): + print(f#quot;Y={y}#quot;) + case (x, 0): + print(f#quot;X={x}#quot;) + case (x, y): + print(f#quot;X={x}, Y={y}#quot;) + case _: + raise ValueError(#quot;Not a point#quot;)\n"] + block6["print(f#quot;X={x}#quot;)\n"] + block7["match point: + case (0, 0): + print(#quot;Origin#quot;) + case (0, y): + print(f#quot;Y={y}#quot;) + case (x, 0): + print(f#quot;X={x}#quot;) + case (x, y): + print(f#quot;X={x}, Y={y}#quot;) + case _: + raise ValueError(#quot;Not a point#quot;)\n"] + block8["print(f#quot;Y={y}#quot;)\n"] + block9["match point: + case (0, 0): + print(#quot;Origin#quot;) + case (0, y): + print(f#quot;Y={y}#quot;) + case (x, 0): + print(f#quot;X={x}#quot;) + case (x, y): + print(f#quot;X={x}, Y={y}#quot;) + case _: + raise ValueError(#quot;Not a point#quot;)\n"] + block10["print(#quot;Origin#quot;)\n"] + block11["match point: + case (0, 0): + print(#quot;Origin#quot;) + case (0, y): + print(f#quot;Y={y}#quot;) + case (x, 0): + print(f#quot;X={x}#quot;) + case (x, y): + print(f#quot;X={x}, Y={y}#quot;) + case _: + raise ValueError(#quot;Not a point#quot;)\n"] + + start --> block11 + block11 -- "case (0, 0)" --> block10 + block11 -- "else" --> block9 + block10 --> block0 + block9 -- "case (0, y)" --> block8 + block9 -- "else" --> block7 + block8 --> block0 + block7 -- "case (x, 0)" --> block6 + block7 -- "else" --> block5 + block6 --> block0 + block5 -- "case (x, y)" --> block4 + block5 -- "else" --> block3 + block4 --> block0 + block3 --> block2 + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 10 +### Source +```python +def where_is(point): + class Point: + x: int + y: int + + match point: + case Point(x=0, y=0): + print("Origin") + case Point(x=0, y=y): + print(f"Y={y}") + case Point(x=x, y=0): + print(f"X={x}") + case Point(): + print("Somewhere else") + case _: + print("Not a point") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;Not a point#quot;)\n"] + block2["match point: + case Point(x=0, y=0): + print(#quot;Origin#quot;) + case Point(x=0, y=y): + print(f#quot;Y={y}#quot;) + case Point(x=x, y=0): + print(f#quot;X={x}#quot;) + case Point(): + print(#quot;Somewhere else#quot;) + case _: + print(#quot;Not a point#quot;)\n"] + block3["print(#quot;Somewhere else#quot;)\n"] + block4["match point: + case Point(x=0, y=0): + print(#quot;Origin#quot;) + case Point(x=0, y=y): + print(f#quot;Y={y}#quot;) + case Point(x=x, y=0): + print(f#quot;X={x}#quot;) + case Point(): + print(#quot;Somewhere else#quot;) + case _: + print(#quot;Not a point#quot;)\n"] + block5["print(f#quot;X={x}#quot;)\n"] + block6["match point: + case Point(x=0, y=0): + print(#quot;Origin#quot;) + case Point(x=0, y=y): + print(f#quot;Y={y}#quot;) + case Point(x=x, y=0): + print(f#quot;X={x}#quot;) + case Point(): + print(#quot;Somewhere else#quot;) + case _: + print(#quot;Not a point#quot;)\n"] + block7["print(f#quot;Y={y}#quot;)\n"] + block8["match point: + case Point(x=0, y=0): + print(#quot;Origin#quot;) + case Point(x=0, y=y): + print(f#quot;Y={y}#quot;) + case Point(x=x, y=0): + print(f#quot;X={x}#quot;) + case Point(): + print(#quot;Somewhere else#quot;) + case _: + print(#quot;Not a point#quot;)\n"] + block9["print(#quot;Origin#quot;)\n"] + block10["match point: + case Point(x=0, y=0): + print(#quot;Origin#quot;) + case Point(x=0, y=y): + print(f#quot;Y={y}#quot;) + case Point(x=x, y=0): + print(f#quot;X={x}#quot;) + case Point(): + print(#quot;Somewhere else#quot;) + case _: + print(#quot;Not a point#quot;)\n"] + block11["class Point: + x: int + y: int\n"] + + start --> block11 + block11 --> block10 + block10 -- "case Point(x=0, y=0)" --> block9 + block10 -- "else" --> block8 + block9 --> block0 + block8 -- "case Point(x=0, y=y)" --> block7 + block8 -- "else" --> block6 + block7 --> block0 + block6 -- "case Point(x=x, y=0)" --> block5 + block6 -- "else" --> block4 + block5 --> block0 + block4 -- "case Point()" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 11 +### Source +```python +def func(points): + match points: + case []: + print("No points") + case [Point(0, 0)]: + print("The origin") + case [Point(x, y)]: + print(f"Single point {x}, {y}") + case [Point(0, y1), Point(0, y2)]: + print(f"Two on the Y axis at {y1}, {y2}") + case _: + print("Something else") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;Something else#quot;)\n"] + block2["match points: + case []: + print(#quot;No points#quot;) + case [Point(0, 0)]: + print(#quot;The origin#quot;) + case [Point(x, y)]: + print(f#quot;Single point {x}, {y}#quot;) + case [Point(0, y1), Point(0, y2)]: + print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) + case _: + print(#quot;Something else#quot;)\n"] + block3["print(f#quot;Two on the Y axis at {y1}, {y2}#quot;)\n"] + block4["match points: + case []: + print(#quot;No points#quot;) + case [Point(0, 0)]: + print(#quot;The origin#quot;) + case [Point(x, y)]: + print(f#quot;Single point {x}, {y}#quot;) + case [Point(0, y1), Point(0, y2)]: + print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) + case _: + print(#quot;Something else#quot;)\n"] + block5["print(f#quot;Single point {x}, {y}#quot;)\n"] + block6["match points: + case []: + print(#quot;No points#quot;) + case [Point(0, 0)]: + print(#quot;The origin#quot;) + case [Point(x, y)]: + print(f#quot;Single point {x}, {y}#quot;) + case [Point(0, y1), Point(0, y2)]: + print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) + case _: + print(#quot;Something else#quot;)\n"] + block7["print(#quot;The origin#quot;)\n"] + block8["match points: + case []: + print(#quot;No points#quot;) + case [Point(0, 0)]: + print(#quot;The origin#quot;) + case [Point(x, y)]: + print(f#quot;Single point {x}, {y}#quot;) + case [Point(0, y1), Point(0, y2)]: + print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) + case _: + print(#quot;Something else#quot;)\n"] + block9["print(#quot;No points#quot;)\n"] + block10["match points: + case []: + print(#quot;No points#quot;) + case [Point(0, 0)]: + print(#quot;The origin#quot;) + case [Point(x, y)]: + print(f#quot;Single point {x}, {y}#quot;) + case [Point(0, y1), Point(0, y2)]: + print(f#quot;Two on the Y axis at {y1}, {y2}#quot;) + case _: + print(#quot;Something else#quot;)\n"] + + start --> block10 + block10 -- "case []" --> block9 + block10 -- "else" --> block8 + block9 --> block0 + block8 -- "case [Point(0, 0)]" --> block7 + block8 -- "else" --> block6 + block7 --> block0 + block6 -- "case [Point(x, y)]" --> block5 + block6 -- "else" --> block4 + block5 --> block0 + block4 -- "case [Point(0, y1), Point(0, y2)]" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 12 +### Source +```python +def func(point): + match point: + case Point(x, y) if x == y: + print(f"Y=X at {x}") + case Point(x, y): + print(f"Not on the diagonal") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(f#quot;Not on the diagonal#quot;)\n"] + block2["match point: + case Point(x, y) if x == y: + print(f#quot;Y=X at {x}#quot;) + case Point(x, y): + print(f#quot;Not on the diagonal#quot;)\n"] + block3["print(f#quot;Y=X at {x}#quot;)\n"] + block4["match point: + case Point(x, y) if x == y: + print(f#quot;Y=X at {x}#quot;) + case Point(x, y): + print(f#quot;Not on the diagonal#quot;)\n"] + + start --> block4 + block4 -- "case Point(x, y) if x == y" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 -- "case Point(x, y)" --> block1 + block2 -- "else" --> block0 + block1 --> block0 + block0 --> return +``` + +## Function 13 +### Source +```python +def func(): + from enum import Enum + class Color(Enum): + RED = 'red' + GREEN = 'green' + BLUE = 'blue' + + color = Color(input("Enter your choice of 'red', 'blue' or 'green': ")) + + match color: + case Color.RED: + print("I see red!") + case Color.GREEN: + print("Grass is green") + case Color.BLUE: + print("I'm feeling the blues :(") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;I'm feeling the blues :(#quot;)\n"] + block2["match color: + case Color.RED: + print(#quot;I see red!#quot;) + case Color.GREEN: + print(#quot;Grass is green#quot;) + case Color.BLUE: + print(#quot;I'm feeling the blues :(#quot;)\n"] + block3["print(#quot;Grass is green#quot;)\n"] + block4["match color: + case Color.RED: + print(#quot;I see red!#quot;) + case Color.GREEN: + print(#quot;Grass is green#quot;) + case Color.BLUE: + print(#quot;I'm feeling the blues :(#quot;)\n"] + block5["print(#quot;I see red!#quot;)\n"] + block6["match color: + case Color.RED: + print(#quot;I see red!#quot;) + case Color.GREEN: + print(#quot;Grass is green#quot;) + case Color.BLUE: + print(#quot;I'm feeling the blues :(#quot;)\n"] + block7["from enum import Enum\nclass Color(Enum): + RED = 'red' + GREEN = 'green' + BLUE = 'blue'\ncolor = Color(input(#quot;Enter your choice of 'red', 'blue' or 'green': #quot;))\n"] + + start --> block7 + block7 --> block6 + block6 -- "case Color.RED" --> block5 + block6 -- "else" --> block4 + block5 --> block0 + block4 -- "case Color.GREEN" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 -- "case Color.BLUE" --> block1 + block2 -- "else" --> block0 + block1 --> block0 + block0 --> return +``` + +## Function 14 +### Source +```python +def func(point): + match point: + case (0, 0): + print("Origin") + case foo: + raise ValueError("oops") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["raise ValueError(#quot;oops#quot;)\n"] + block3["match point: + case (0, 0): + print(#quot;Origin#quot;) + case foo: + raise ValueError(#quot;oops#quot;)\n"] + block4["print(#quot;Origin#quot;)\n"] + block5["match point: + case (0, 0): + print(#quot;Origin#quot;) + case foo: + raise ValueError(#quot;oops#quot;)\n"] + + start --> block5 + block5 -- "case (0, 0)" --> block4 + block5 -- "else" --> block3 + block4 --> block0 + block3 --> block2 + block2 --> block1 + block1 --> return + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__raise.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__raise.py.md.snap new file mode 100644 index 0000000000000..3f3c1c3ceb912 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__raise.py.md.snap @@ -0,0 +1,43 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + raise Exception +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["Exception raised"]] + block1["raise Exception\n"] + + start --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + raise "a glass!" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["Exception raised"]] + block1["raise #quot;a glass!#quot;\n"] + + start --> block1 + block1 --> block0 + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__simple.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__simple.py.md.snap new file mode 100644 index 0000000000000..8441684bbe436 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__simple.py.md.snap @@ -0,0 +1,188 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + pass +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["pass\n"] + + start --> block0 + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + pass +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["pass\n"] + + start --> block0 + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + return +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return\n"] + + start --> block0 + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 1\n"] + + start --> block0 + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + return 1 + return "unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;unreachable#quot;\n"] + block1["return 1\n"] + + start --> block1 + block1 --> return + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + i = 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["i = 0\n"] + + start --> block0 + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + i = 0 + i += 2 + return i +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["i = 0\ni += 2\nreturn i\n"] + + start --> block0 + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + with x: + i = 0 + i = 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["i = 1\n"] + block1["i = 0\n"] + block2["with x: + i = 0\n"] + + start --> block2 + block2 -- "Exception raised" --> block0 + block2 -- "else" --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 8 +### Source +```python +def func(): + with x: + i = 0 + return 1 + i = 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["i = 1\n"] + block1["i = 0\nreturn 1\n"] + block2["with x: + i = 0 + return 1\n"] + + start --> block2 + block2 -- "Exception raised" --> block0 + block2 -- "else" --> block1 + block1 --> return + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try.py.md.snap new file mode 100644 index 0000000000000..47720ae8379df --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try.py.md.snap @@ -0,0 +1,710 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + try: + print("try") + except Exception: + print("Exception") + except OtherException as e: + print("OtherException") + else: + print("else") + finally: + print("finally") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;finally#quot;)\n"] + block2["print(#quot;else#quot;)\n"] + block3["print(#quot;try#quot;)\n"] + block4[["Exception raised"]] + block5["print(#quot;OtherException#quot;)\n"] + block6["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;) + else: + print(#quot;else#quot;) + finally: + print(#quot;finally#quot;)\n"] + block7["print(#quot;Exception#quot;)\n"] + block8["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;) + else: + print(#quot;else#quot;) + finally: + print(#quot;finally#quot;)\n"] + block9["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;) + else: + print(#quot;else#quot;) + finally: + print(#quot;finally#quot;)\n"] + + start --> block9 + block9 -- "Exception raised" --> block8 + block9 -- "else" --> block3 + block8 -- "Exception" --> block7 + block8 -- "else" --> block6 + block7 --> block1 + block6 -- "OtherException" --> block5 + block6 -- "else" --> block4 + block5 --> block1 + block4 --> block1 + block3 --> block2 + block2 --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + try: + print("try") + except: + print("Exception") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;try#quot;)\n"] + block2[["Exception raised"]] + block3["print(#quot;Exception#quot;)\n"] + block4["try: + print(#quot;try#quot;) + except: + print(#quot;Exception#quot;)\n"] + + start --> block4 + block4 -- "Exception raised" --> block3 + block4 -- "else" --> block1 + block3 --> block0 + block2 --> return + block1 --> block0 + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + try: + print("try") + except: + print("Exception") + except OtherException as e: + print("OtherException") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;try#quot;)\n"] + block2[["Exception raised"]] + block3["print(#quot;OtherException#quot;)\n"] + block4["try: + print(#quot;try#quot;) + except: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;)\n"] + block5["print(#quot;Exception#quot;)\n"] + block6["try: + print(#quot;try#quot;) + except: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;)\n"] + + start --> block6 + block6 -- "Exception raised" --> block5 + block6 -- "else" --> block1 + block5 --> block0 + block4 -- "OtherException" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 --> return + block1 --> block0 + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + try: + print("try") + except Exception: + print("Exception") + except OtherException as e: + print("OtherException") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;try#quot;)\n"] + block2[["Exception raised"]] + block3["print(#quot;OtherException#quot;)\n"] + block4["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;)\n"] + block5["print(#quot;Exception#quot;)\n"] + block6["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;)\n"] + block7["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;)\n"] + + start --> block7 + block7 -- "Exception raised" --> block6 + block7 -- "else" --> block1 + block6 -- "Exception" --> block5 + block6 -- "else" --> block4 + block5 --> block0 + block4 -- "OtherException" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 --> return + block1 --> block0 + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + try: + print("try") + except Exception: + print("Exception") + except OtherException as e: + print("OtherException") + else: + print("else") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;else#quot;)\n"] + block2["print(#quot;try#quot;)\n"] + block3[["Exception raised"]] + block4["print(#quot;OtherException#quot;)\n"] + block5["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;) + else: + print(#quot;else#quot;)\n"] + block6["print(#quot;Exception#quot;)\n"] + block7["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;) + else: + print(#quot;else#quot;)\n"] + block8["try: + print(#quot;try#quot;) + except Exception: + print(#quot;Exception#quot;) + except OtherException as e: + print(#quot;OtherException#quot;) + else: + print(#quot;else#quot;)\n"] + + start --> block8 + block8 -- "Exception raised" --> block7 + block8 -- "else" --> block2 + block7 -- "Exception" --> block6 + block7 -- "else" --> block5 + block6 --> block0 + block5 -- "OtherException" --> block4 + block5 -- "else" --> block3 + block4 --> block0 + block3 --> return + block2 --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + try: + print("try") + finally: + print("finally") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;finally#quot;)\n"] + block2["print(#quot;try#quot;)\n"] + block3[["Exception raised"]] + block4["try: + print(#quot;try#quot;) + finally: + print(#quot;finally#quot;)\n"] + + start --> block4 + block4 -- "Exception raised" --> block3 + block4 -- "else" --> block2 + block3 --> block1 + block2 --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + try: + return 0 + except: + return 1 + finally: + return 2 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 2\n"] + block2["return 0\n"] + block3[["Exception raised"]] + block4["return 1\n"] + block5["try: + return 0 + except: + return 1 + finally: + return 2\n"] + + start --> block5 + block5 -- "Exception raised" --> block4 + block5 -- "else" --> block2 + block4 --> block1 + block3 --> block1 + block2 --> block1 + block1 --> return + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + try: + raise Exception() + except: + print("reached") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["raise Exception()\n"] + block3[["Exception raised"]] + block4["print(#quot;reached#quot;)\n"] + block5["try: + raise Exception() + except: + print(#quot;reached#quot;)\n"] + + start --> block5 + block5 -- "Exception raised" --> block4 + block5 -- "else" --> block2 + block4 --> block0 + block3 --> return + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 8 +### Source +```python +def func(): + try: + assert False + print("unreachable") + except: + print("reached") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;unreachable#quot;)\n"] + block2[["Exception raised"]] + block3["assert False\n"] + block4[["Exception raised"]] + block5["print(#quot;reached#quot;)\n"] + block6["try: + assert False + print(#quot;unreachable#quot;) + except: + print(#quot;reached#quot;)\n"] + + start --> block6 + block6 -- "Exception raised" --> block5 + block6 -- "else" --> block3 + block5 --> block0 + block4 --> return + block3 -- "False" --> block1 + block3 -- "else" --> block5 + block2 --> return + block1 --> block0 + block0 --> return +``` + +## Function 9 +### Source +```python +def func(): + try: + raise Exception() + finally: + print('reached') + return 2 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print('reached')\nreturn 2\n"] + block2[["Exception raised"]] + block3["raise Exception()\n"] + block4[["Exception raised"]] + block5["try: + raise Exception() + finally: + print('reached') + return 2\n"] + + start --> block5 + block5 -- "Exception raised" --> block4 + block5 -- "else" --> block3 + block4 --> block1 + block3 --> block1 + block2 --> return + block1 --> return + block0 --> return +``` + +## Function 10 +### Source +```python +def func(): + try: + assert False + print("unreachable") + finally: + print("reached") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["print(#quot;reached#quot;)\n"] + block2["print(#quot;unreachable#quot;)\n"] + block3[["Exception raised"]] + block4["assert False\n"] + block5[["Exception raised"]] + block6["try: + assert False + print(#quot;unreachable#quot;) + finally: + print(#quot;reached#quot;)\n"] + + start --> block6 + block6 -- "Exception raised" --> block5 + block6 -- "else" --> block4 + block5 --> block1 + block4 -- "False" --> block2 + block4 -- "else" --> block1 + block3 --> return + block2 --> block1 + block1 --> block0 + block0 --> return +``` + +## Function 11 +### Source +```python +def func(): + try: + if catalog is not None: + try: + x = 0 + except PySparkParseException: + x = 1 + try: + x = 2 + except PySparkParseException: + x = 3 + x = 8 + finally: + if catalog is not None: + try: + x = 4 + except PySparkParseException: + x = 5 + try: + x = 6 + except PySparkParseException: + x = 7 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["x = 6\n"] + block2[["Exception raised"]] + block3["x = 7\n"] + block4["try: + x = 6 + except PySparkParseException: + x = 7\n"] + block5["try: + x = 6 + except PySparkParseException: + x = 7\n"] + block6["x = 4\n"] + block7[["Exception raised"]] + block8["x = 5\n"] + block9["try: + x = 4 + except PySparkParseException: + x = 5\n"] + block10["try: + x = 4 + except PySparkParseException: + x = 5\n"] + block11["if catalog is not None: + try: + x = 4 + except PySparkParseException: + x = 5\n"] + block12["x = 8\n"] + block13["x = 2\n"] + block14[["Exception raised"]] + block15["x = 3\n"] + block16["try: + x = 2 + except PySparkParseException: + x = 3\n"] + block17["try: + x = 2 + except PySparkParseException: + x = 3\n"] + block18["x = 0\n"] + block19[["Exception raised"]] + block20["x = 1\n"] + block21["try: + x = 0 + except PySparkParseException: + x = 1\n"] + block22["try: + x = 0 + except PySparkParseException: + x = 1\n"] + block23["if catalog is not None: + try: + x = 0 + except PySparkParseException: + x = 1\n"] + block24[["Exception raised"]] + block25["try: + if catalog is not None: + try: + x = 0 + except PySparkParseException: + x = 1 + try: + x = 2 + except PySparkParseException: + x = 3 + x = 8 + finally: + if catalog is not None: + try: + x = 4 + except PySparkParseException: + x = 5 + try: + x = 6 + except PySparkParseException: + x = 7\n"] + + start --> block25 + block25 -- "Exception raised" --> block24 + block25 -- "else" --> block23 + block24 --> block11 + block23 -- "catalog is not None" --> block22 + block23 -- "else" --> block17 + block22 -- "Exception raised" --> block21 + block22 -- "else" --> block18 + block21 -- "PySparkParseException" --> block20 + block21 -- "else" --> block19 + block20 --> block17 + block19 --> block11 + block18 --> block17 + block17 -- "Exception raised" --> block16 + block17 -- "else" --> block13 + block16 -- "PySparkParseException" --> block15 + block16 -- "else" --> block14 + block15 --> block12 + block14 --> block11 + block13 --> block12 + block12 --> block11 + block11 -- "catalog is not None" --> block10 + block11 -- "else" --> block5 + block10 -- "Exception raised" --> block9 + block10 -- "else" --> block6 + block9 -- "PySparkParseException" --> block8 + block9 -- "else" --> block7 + block8 --> block5 + block7 --> return + block6 --> block5 + block5 -- "Exception raised" --> block4 + block5 -- "else" --> block1 + block4 -- "PySparkParseException" --> block3 + block4 -- "else" --> block2 + block3 --> block0 + block2 --> return + block1 --> block0 + block0 --> return +``` + +## Function 12 +### Source +```python +def func(): + try: + assert False + except ex: + raise ex + + finally: + raise Exception("other") +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Exception raised"]] + block2["raise Exception(#quot;other#quot;)\n"] + block3[["Exception raised"]] + block4["assert False\n"] + block5[["Exception raised"]] + block6[["Exception raised"]] + block7["raise ex\n"] + block8["try: + assert False + except ex: + raise ex + + finally: + raise Exception(#quot;other#quot;)\n"] + block9["try: + assert False + except ex: + raise ex + + finally: + raise Exception(#quot;other#quot;)\n"] + + start --> block9 + block9 -- "Exception raised" --> block8 + block9 -- "else" --> block4 + block8 -- "ex" --> block7 + block8 -- "else" --> block5 + block7 --> block2 + block6 --> return + block5 --> block2 + block4 -- "False" --> block2 + block4 -- "else" --> block8 + block3 --> return + block2 --> block1 + block1 --> return + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__while.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__while.py.md.snap new file mode 100644 index 0000000000000..5e05666e8fcbe --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__while.py.md.snap @@ -0,0 +1,779 @@ +--- +source: crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." +--- +## Function 0 +### Source +```python +def func(): + while False: + return "unreachable" + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 1\n"] + block1[["Loop continue"]] + block2["return #quot;unreachable#quot;\n"] + block3["while False: + return #quot;unreachable#quot;\n"] + + start --> block3 + block3 -- "False" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> block3 + block0 --> return +``` + +## Function 1 +### Source +```python +def func(): + while False: + return "unreachable" + else: + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return 1\n"] + block2[["Loop continue"]] + block3["return #quot;unreachable#quot;\n"] + block4["while False: + return #quot;unreachable#quot; + else: + return 1\n"] + + start --> block4 + block4 -- "False" --> block3 + block4 -- "else" --> block1 + block3 --> return + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 2 +### Source +```python +def func(): + while False: + return "unreachable" + else: + return 1 + return "also unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;also unreachable#quot;\n"] + block1["return 1\n"] + block2[["Loop continue"]] + block3["return #quot;unreachable#quot;\n"] + block4["while False: + return #quot;unreachable#quot; + else: + return 1\n"] + + start --> block4 + block4 -- "False" --> block3 + block4 -- "else" --> block1 + block3 --> return + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 3 +### Source +```python +def func(): + while True: + return 1 + return "unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;unreachable#quot;\n"] + block1[["Loop continue"]] + block2["return 1\n"] + block3["while True: + return 1\n"] + + start --> block3 + block3 -- "True" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> block3 + block0 --> return +``` + +## Function 4 +### Source +```python +def func(): + while True: + return 1 + else: + return "unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1["return #quot;unreachable#quot;\n"] + block2[["Loop continue"]] + block3["return 1\n"] + block4["while True: + return 1 + else: + return #quot;unreachable#quot;\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block1 + block3 --> return + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 5 +### Source +```python +def func(): + while True: + return 1 + else: + return "unreachable" + return "also unreachable" +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return #quot;also unreachable#quot;\n"] + block1["return #quot;unreachable#quot;\n"] + block2[["Loop continue"]] + block3["return 1\n"] + block4["while True: + return 1 + else: + return #quot;unreachable#quot;\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block1 + block3 --> return + block2 --> block4 + block1 --> return + block0 --> return +``` + +## Function 6 +### Source +```python +def func(): + i = 0 + while False: + i += 1 + return i +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return i\n"] + block1[["Loop continue"]] + block2["i += 1\n"] + block3["i = 0\nwhile False: + i += 1\n"] + + start --> block3 + block3 -- "False" --> block2 + block3 -- "else" --> block0 + block2 --> block1 + block1 --> block3 + block0 --> return +``` + +## Function 7 +### Source +```python +def func(): + i = 0 + while True: + i += 1 + return i +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return i\n"] + block1[["Loop continue"]] + block2["i += 1\n"] + block3["i = 0\nwhile True: + i += 1\n"] + + start --> block3 + block3 -- "True" --> block2 + block3 -- "else" --> block0 + block2 --> block1 + block1 --> block3 + block0 --> return +``` + +## Function 8 +### Source +```python +def func(): + while True: + pass + return 1 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 1\n"] + block1[["Loop continue"]] + block2["pass\n"] + block3["while True: + pass\n"] + + start --> block3 + block3 -- "True" --> block2 + block3 -- "else" --> block0 + block2 --> block1 + block1 --> block3 + block0 --> return +``` + +## Function 9 +### Source +```python +def func(): + i = 0 + while True: + if True: + print("ok") + i += 1 + return i +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return i\n"] + block1[["Loop continue"]] + block2["i += 1\n"] + block3["print(#quot;ok#quot;)\n"] + block4["if True: + print(#quot;ok#quot;)\n"] + block5["i = 0\nwhile True: + if True: + print(#quot;ok#quot;) + i += 1\n"] + + start --> block5 + block5 -- "True" --> block4 + block5 -- "else" --> block0 + block4 -- "True" --> block3 + block4 -- "else" --> block2 + block3 --> block2 + block2 --> block1 + block1 --> block5 + block0 --> return +``` + +## Function 10 +### Source +```python +def func(): + i = 0 + while True: + if False: + print("ok") + i += 1 + return i +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return i\n"] + block1[["Loop continue"]] + block2["i += 1\n"] + block3["print(#quot;ok#quot;)\n"] + block4["if False: + print(#quot;ok#quot;)\n"] + block5["i = 0\nwhile True: + if False: + print(#quot;ok#quot;) + i += 1\n"] + + start --> block5 + block5 -- "True" --> block4 + block5 -- "else" --> block0 + block4 -- "False" --> block3 + block4 -- "else" --> block2 + block3 --> block2 + block2 --> block1 + block1 --> block5 + block0 --> return +``` + +## Function 11 +### Source +```python +def func(): + while True: + if True: + return 1 + return 0 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["return 0\n"] + block1[["Loop continue"]] + block2["return 1\n"] + block3["if True: + return 1\n"] + block4["while True: + if True: + return 1\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> block4 + block0 --> return +``` + +## Function 12 +### Source +```python +def func(): + while True: + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["while True: + continue\n"] + + start --> block3 + block3 -- "True" --> block2 + block3 -- "else" --> block0 + block2 --> block3 + block1 --> block3 + block0 --> return +``` + +## Function 13 +### Source +```python +def func(): + while False: + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["while False: + continue\n"] + + start --> block3 + block3 -- "False" --> block2 + block3 -- "else" --> block0 + block2 --> block3 + block1 --> block3 + block0 --> return +``` + +## Function 14 +### Source +```python +def func(): + while True: + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["while True: + break\n"] + + start --> block3 + block3 -- "True" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> block3 + block0 --> return +``` + +## Function 15 +### Source +```python +def func(): + while False: + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["while False: + break\n"] + + start --> block3 + block3 -- "False" --> block2 + block3 -- "else" --> block0 + block2 --> return + block1 --> block3 + block0 --> return +``` + +## Function 16 +### Source +```python +def func(): + while True: + if True: + continue +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["continue\n"] + block3["if True: + continue\n"] + block4["while True: + if True: + continue\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> block4 + block1 --> block4 + block0 --> return +``` + +## Function 17 +### Source +```python +def func(): + while True: + if True: + break +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0[["`*(empty)*`"]] + block1[["Loop continue"]] + block2["break\n"] + block3["if True: + break\n"] + block4["while True: + if True: + break\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block0 + block3 -- "True" --> block2 + block3 -- "else" --> block1 + block2 --> return + block1 --> block4 + block0 --> return +``` + +## Function 18 +### Source +```python +def func(): + while True: + x = 0 + x = 1 + break + x = 2 + x = 3 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["x = 3\n"] + block1[["Loop continue"]] + block2["x = 2\n"] + block3["x = 0\nx = 1\nbreak\n"] + block4["while True: + x = 0 + x = 1 + break + x = 2\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block0 + block3 --> block0 + block2 --> block1 + block1 --> block4 + block0 --> return +``` + +## Function 19 +### Source +```python +def func(): + while True: + x = 0 + x = 1 + continue + x = 2 + x = 3 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["x = 3\n"] + block1[["Loop continue"]] + block2["x = 2\n"] + block3["x = 0\nx = 1\ncontinue\n"] + block4["while True: + x = 0 + x = 1 + continue + x = 2\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block0 + block3 --> block4 + block2 --> block1 + block1 --> block4 + block0 --> return +``` + +## Function 20 +### Source +```python +def func(): + while True: + x = 0 + x = 1 + return + x = 2 + x = 3 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["x = 3\n"] + block1[["Loop continue"]] + block2["x = 2\n"] + block3["x = 0\nx = 1\nreturn\n"] + block4["while True: + x = 0 + x = 1 + return + x = 2\n"] + + start --> block4 + block4 -- "True" --> block3 + block4 -- "else" --> block0 + block3 --> return + block2 --> block1 + block1 --> block4 + block0 --> return +``` + +## Function 21 +### Source +```python +def func(): + while True: + x = 0 + x = 1 + raise Exception + x = 2 + x = 3 +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["x = 3\n"] + block1[["Loop continue"]] + block2["x = 2\n"] + block3[["Exception raised"]] + block4["x = 0\nx = 1\nraise Exception\n"] + block5["while True: + x = 0 + x = 1 + raise Exception + x = 2\n"] + + start --> block5 + block5 -- "True" --> block4 + block5 -- "else" --> block0 + block4 --> block3 + block3 --> return + block2 --> block1 + block1 --> block5 + block0 --> return +``` + +## Function 22 +### Source +```python +def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None: + self.stop_serving = False + while True: + try: + self.server = HTTPServer((host, port), HtmlOnlyHandler) + self.host = host + self.port = port + break + except OSError: + log.debug(f"port {port} is in use, trying to next one") + port += 1 + + self.thread = threading.Thread(target=self._run_web_server) +``` + +### Control Flow Graph +```mermaid +flowchart TD + start(("Start")) + return(("End")) + block0["self.thread = threading.Thread(target=self._run_web_server)\n"] + block1[["Loop continue"]] + block2["self.server = HTTPServer((host, port), HtmlOnlyHandler)\nself.host = host\nself.port = port\nbreak\n"] + block3[["Exception raised"]] + block4["log.debug(f#quot;port {port} is in use, trying to next one#quot;)\nport += 1\n"] + block5["try: + self.server = HTTPServer((host, port), HtmlOnlyHandler) + self.host = host + self.port = port + break + except OSError: + log.debug(f#quot;port {port} is in use, trying to next one#quot;) + port += 1\n"] + block6["try: + self.server = HTTPServer((host, port), HtmlOnlyHandler) + self.host = host + self.port = port + break + except OSError: + log.debug(f#quot;port {port} is in use, trying to next one#quot;) + port += 1\n"] + block7["self.stop_serving = False\nwhile True: + try: + self.server = HTTPServer((host, port), HtmlOnlyHandler) + self.host = host + self.port = port + break + except OSError: + log.debug(f#quot;port {port} is in use, trying to next one#quot;) + port += 1\n"] + + start --> block7 + block7 -- "True" --> block6 + block7 -- "else" --> block0 + block6 -- "Exception raised" --> block5 + block6 -- "else" --> block2 + block5 -- "OSError" --> block4 + block5 -- "else" --> block3 + block4 --> block1 + block3 --> return + block2 --> block0 + block1 --> block7 + block0 --> return +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs new file mode 100644 index 0000000000000..4e489f4e221f6 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs @@ -0,0 +1,1217 @@ +use std::cmp; + +use ruff_python_ast::{ + self as ast, Expr, ExprBooleanLiteral, Identifier, MatchCase, Pattern, PatternMatchAs, + PatternMatchOr, Stmt, StmtContinue, StmtFor, StmtMatch, StmtReturn, StmtTry, StmtWhile, + StmtWith, +}; +use ruff_text_size::{Ranged, TextRange, TextSize}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_index::{IndexSlice, IndexVec}; +use ruff_macros::{derive_message_formats, newtype_index, ViolationMetadata}; + +/// ## What it does +/// Checks for unreachable code. +/// +/// ## Why is this bad? +/// Unreachable code can be a maintenance burden without ever being used. +/// +/// ## Example +/// ```python +/// def function(): +/// if False: +/// return "unreachable" +/// return "reachable" +/// ``` +/// +/// Use instead: +/// ```python +/// def function(): +/// return "reachable" +/// ``` +#[derive(ViolationMetadata)] +pub(crate) struct UnreachableCode { + name: String, +} + +impl Violation for UnreachableCode { + #[derive_message_formats] + fn message(&self) -> String { + let UnreachableCode { name } = self; + format!("Unreachable code in `{name}`") + } +} + +pub(crate) fn in_function(name: &Identifier, body: &[Stmt]) -> Vec { + // Create basic code blocks from the body. + let mut basic_blocks = BasicBlocks::from(body); + if let Some(start_index) = basic_blocks.start_index() { + mark_reachable(&mut basic_blocks.blocks, start_index); + } + + let mut diagnostics: Vec = Vec::new(); + + // Combine sequential unreachable blocks + let mut blocks = basic_blocks.blocks.raw; + blocks.sort_by_key(|a| a.start().to_u32()); + let mut start = None; + let mut end = None; + for block in blocks { + if block.is_sentinel() { + continue; + } + + if block.reachable { + // At each reachable block, create a violation for all the + // unreachable blocks encountered since the last reachable + // block. + if let Some(start_index) = start { + if let Some(end_index) = end { + // TODO: add more information to the diagnostic. + // Maybe something to indicate the code flow and where it + // prevents this block from being reached for example. + let diagnostic = Diagnostic::new( + UnreachableCode { + name: name.as_str().to_owned(), + }, + TextRange::new(start_index, end_index), + ); + diagnostics.push(diagnostic); + + start = None; + end = None; + } + } + } else { + if let Some(end_index) = end { + end = Some(cmp::max(block.end(), end_index)); + } else { + start = Some(block.start()); + end = Some(block.end()); + } + } + } + if let Some(start_index) = start { + if let Some(end_index) = end { + let diagnostic = Diagnostic::new( + UnreachableCode { + name: name.as_str().to_owned(), + }, + TextRange::new(start_index, end_index), + ); + diagnostics.push(diagnostic); + } + } + diagnostics +} + +/// Set bits in `reached_map` for all blocks that are reached in `blocks` +/// starting with block at index `idx`. +fn mark_reachable(blocks: &mut IndexSlice>, start_index: BlockIndex) { + let mut idx = start_index; + + loop { + if blocks[idx].reachable { + return; // Block already visited, no needed to do it again. + } + blocks[idx].reachable = true; + + match &blocks[idx].next { + NextBlock::Always(next) => idx = *next, + NextBlock::If { + condition, + next, + orelse, + .. + } => { + match taken(condition) { + Some(true) => idx = *next, // Always taken. + Some(false) => idx = *orelse, // Never taken. + None => { + // Don't know, both branches might be taken. + idx = *next; + mark_reachable(blocks, *orelse); + } + } + } + NextBlock::Terminate => return, + } + } +} + +/// Determines if `condition` is taken. +/// Returns `Some(true)` if the condition is always true, e.g. `if True`, same +/// with `Some(false)` if it's never taken. If it can't be determined it returns +/// `None`, e.g. `if i == 100`. +fn taken(condition: &Condition) -> Option { + // TODO: add more cases to this where we can determine a condition + // statically. For now we only consider constant booleans. + match condition { + Condition::Test(expr) => match expr { + Expr::BooleanLiteral(ExprBooleanLiteral { value, .. }) => Some(*value), + _ => None, + }, + Condition::Iterator(_) => None, + Condition::Match { .. } => None, + Condition::Except(_) => None, + Condition::MaybeRaised => None, + } +} + +/// Index into [`BasicBlocks::blocks`]. +#[newtype_index] +#[derive(PartialOrd, Ord)] +struct BlockIndex; + +#[derive(Debug, PartialEq, Clone)] +enum BasicBlockKind { + Generic, + Empty, + Exception, + LoopContinue, +} + +/// Collection of basic block. +#[derive(Debug, PartialEq)] +struct BasicBlocks<'stmt> { + /// # Notes + /// + /// The order of these block is unspecified. However it's guaranteed that + /// the last block is the first statement in the function and the first + /// block is the last statement. The block are more or less in reverse + /// order, but it gets fussy around control flow statements (e.g. `while` + /// statements). + /// + /// For loop blocks (e.g. `while` and `for`), the end of the body will + /// point to the loop block again (to create the loop). However an oddity + /// here is that this block might contain statements before the loop + /// itself which, of course, won't be executed again. + /// + /// For example: + /// ```python + /// i = 0 # block 0 + /// while True: # + /// continue # block 1 + /// ``` + /// Will create a connection between block 1 (loop body) and block 0, which + /// includes the `i = 0` statement. + /// + /// To keep `NextBlock` simple(r) `NextBlock::If`'s `next` and `orelse` + /// fields only use `BlockIndex`, which means that they can't terminate + /// themselves. To support this we insert *empty*/fake blocks before the end + /// of the function that we can link to. + /// + /// Finally `BasicBlock` can also be a sentinel node, see the associated + /// constants of [`BasicBlock`]. + blocks: IndexVec>, +} + +impl BasicBlocks<'_> { + fn start_index(&self) -> Option { + self.blocks.indices().last() + } +} + +impl<'stmt> From<&'stmt [Stmt]> for BasicBlocks<'stmt> { + /// # Notes + /// + /// This assumes that `stmts` is a function body. + fn from(stmts: &'stmt [Stmt]) -> BasicBlocks<'stmt> { + let mut blocks = BasicBlocksBuilder::with_capacity(stmts.len()); + blocks.create_blocks(stmts, None); + blocks.finish() + } +} + +/// Basic code block, sequence of statements unconditionally executed +/// "together". +#[derive(Clone, Debug, PartialEq)] +struct BasicBlock<'stmt> { + stmts: &'stmt [Stmt], + next: NextBlock<'stmt>, + reachable: bool, + kind: BasicBlockKind, +} + +/// Edge between basic blocks (in the control-flow graph). +#[derive(Clone, Debug, PartialEq)] +enum NextBlock<'stmt> { + /// Always continue with a block. + Always(BlockIndex), + /// Condition jump. + If { + /// Condition that needs to be evaluated to jump to the `next` or + /// `orelse` block. + condition: Condition<'stmt>, + /// Next block if `condition` is true. + next: BlockIndex, + /// Next block if `condition` is false. + orelse: BlockIndex, + /// Exit block. None indicates Terminate. + /// The purpose of the `exit` block is to facilitate post processing + /// steps. When iterating over `if` or `try` bodies it is necessary + /// to know when we have exited the body. To avoid reprocessing blocks. + /// + /// For example: + /// ```python + /// while True: # block 0 + /// if True: # block 1 + /// x = 2 # block 2 + /// y = 2 # block 3 + /// z = 2 # block 4 + /// ``` + /// + /// Recursive processing will proceed as follows: + /// block 0 -> block 1 -> block 2 -> block 3 -> block 4 -> Terminate + /// -> block 3 -> block 4 -> Terminate + /// + /// To avoid repeated work we remember that the `if` body exits on + /// block 3, so the recursion can be terminated. + exit: Option, + }, + /// The end. + Terminate, +} + +/// Condition used to determine to take the `next` or `orelse` branch in +/// [`NextBlock::If`]. +#[derive(Clone, Debug, PartialEq)] +enum Condition<'stmt> { + /// Conditional statement, this should evaluate to a boolean, for e.g. `if` + /// or `while`. + Test(&'stmt Expr), + /// Iterator for `for` statements, e.g. for `for i in range(10)` this will be + /// `range(10)`. + Iterator(&'stmt Expr), + Match { + /// `match $subject`. + subject: &'stmt Expr, + /// `case $case`, include pattern, guard, etc. + case: &'stmt MatchCase, + }, + /// Exception was raised and caught by `except` clause. + /// If the raised `Exception` matches the one caught by the `except` + /// then execute the `except` body, otherwise go to the next `except`. + /// + /// The `stmt` is the exception caught by the `except`. + Except(&'stmt Expr), + /// Exception was raised in a `try` block. + /// This condition cannot be evaluated since it's impossible to know + /// (in most cases) if an exception will be raised. So both paths + /// (raise and not-raise) are assumed to be taken. + MaybeRaised, +} + +impl Ranged for Condition<'_> { + fn range(&self) -> TextRange { + match self { + Condition::Test(expr) | Condition::Iterator(expr) | Condition::Except(expr) => { + expr.range() + } + // The case of the match statement, without the body. + Condition::Match { subject: _, case } => TextRange::new( + case.start(), + case.guard + .as_ref() + .map_or(case.pattern.end(), |guard| guard.end()), + ), + Condition::MaybeRaised => TextRange::new(TextSize::new(0), TextSize::new(0)), + } + } +} + +impl<'stmt> BasicBlock<'stmt> { + fn new(stmts: &'stmt [Stmt], next: NextBlock<'stmt>) -> Self { + Self { + stmts, + next, + reachable: false, + kind: BasicBlockKind::Generic, + } + } + + /// A sentinel block indicating an empty termination block. + const EMPTY: BasicBlock<'static> = BasicBlock { + stmts: &[], + next: NextBlock::Terminate, + reachable: false, + kind: BasicBlockKind::Empty, + }; + + /// A sentinel block indicating an exception was raised. + /// This is useful for redirecting flow within `try` blocks. + const EXCEPTION: BasicBlock<'static> = BasicBlock { + stmts: &[Stmt::Return(StmtReturn { + range: TextRange::new(TextSize::new(0), TextSize::new(0)), + value: None, + })], + next: NextBlock::Terminate, + reachable: false, + kind: BasicBlockKind::Exception, + }; + + /// A sentinel block indicating a loop will restart. + /// This is useful for redirecting flow within `while` and + /// `for` blocks. + const LOOP_CONTINUE: BasicBlock<'static> = BasicBlock { + stmts: &[Stmt::Continue(StmtContinue { + range: TextRange::new(TextSize::new(0), TextSize::new(0)), + })], + next: NextBlock::Terminate, // This must be updated dynamically + reachable: false, + kind: BasicBlockKind::LoopContinue, + }; + + /// Return true if the block is a sentinel or fake block. + fn is_sentinel(&self) -> bool { + self.is_empty() || self.is_exception() || self.is_loop_continue() + } + + /// Returns true if `self` is an `EMPTY` block. + fn is_empty(&self) -> bool { + matches!(self.kind, BasicBlockKind::Empty) + } + + /// Returns true if `self` is an `EXCEPTION` block. + fn is_exception(&self) -> bool { + matches!(self.kind, BasicBlockKind::Exception) + } + + /// Returns true if `self` is a `LOOP_CONTINUE` block. + fn is_loop_continue(&self) -> bool { + matches!(self.kind, BasicBlockKind::LoopContinue) + } +} + +impl Ranged for BasicBlock<'_> { + fn range(&self) -> TextRange { + let Some(first) = self.stmts.first() else { + return TextRange::new(TextSize::new(0), TextSize::new(0)); + }; + let Some(last) = self.stmts.last() else { + return TextRange::new(TextSize::new(0), TextSize::new(0)); + }; + TextRange::new(first.start(), last.end()) + } +} + +/// Handle a loop block, such as a `while`, `for`, or `async for` statement. +fn loop_block<'stmt>( + blocks: &mut BasicBlocksBuilder<'stmt>, + condition: Condition<'stmt>, + body: &'stmt [Stmt], + orelse: &'stmt [Stmt], + after: Option, +) -> NextBlock<'stmt> { + let after_block = blocks.find_next_block_index(after); + let last_orelse_statement = blocks.append_blocks_if_not_empty(orelse, after_block); + + let loop_continue_index = blocks.create_loop_continue_block(); + let last_statement_index = blocks.append_blocks_if_not_empty(body, loop_continue_index); + blocks.blocks[loop_continue_index].next = NextBlock::Always(blocks.blocks.next_index()); + + post_process_loop( + blocks, + last_statement_index, + blocks.blocks.next_index(), + after, + after, + ); + + NextBlock::If { + condition, + next: last_statement_index, + orelse: last_orelse_statement, + exit: after, + } +} + +/// Step through the loop in the forward direction so that `break` +/// and `continue` can be correctly directed now that the loop start +/// and exit have been established. +fn post_process_loop( + blocks: &mut BasicBlocksBuilder<'_>, + start_index: BlockIndex, + loop_start: BlockIndex, + loop_exit: Option, + clause_exit: Option, +) { + let mut idx = start_index; + + loop { + if Some(idx) == clause_exit || idx == loop_start { + return; + } + + let block = &mut blocks.blocks[idx]; + + if block.is_loop_continue() { + return; + } + + match block.next { + NextBlock::Always(next) => { + match block.stmts.last() { + Some(Stmt::Break(_)) => { + block.next = match loop_exit { + Some(exit) => NextBlock::Always(exit), + None => NextBlock::Terminate, + } + } + Some(Stmt::Continue(_)) => { + block.next = NextBlock::Always(loop_start); + } + _ => {} + }; + idx = next; + } + NextBlock::If { + condition: _, + next, + orelse, + exit, + } => { + match block.stmts.last() { + Some(Stmt::For(_) | Stmt::While(_)) => { + idx = orelse; + } + Some(Stmt::Assert(_)) => { + post_process_loop(blocks, orelse, loop_start, loop_exit, exit); + idx = next; + } + _ => { + post_process_loop(blocks, next, loop_start, loop_exit, exit); + idx = orelse; + } + }; + } + NextBlock::Terminate => return, + } + } +} + +/// Handle a try block. +fn try_block<'stmt>( + blocks: &mut BasicBlocksBuilder<'stmt>, + stmt: &'stmt Stmt, + after: Option, +) -> NextBlock<'stmt> { + let stmts = std::slice::from_ref(stmt); + let Stmt::Try(StmtTry { + body, + handlers, + orelse, + finalbody, + .. + }) = stmt + else { + panic!("Should only be called with StmtTry."); + }; + + let after_block = blocks.find_next_block_index(after); + let finally_block = blocks.append_blocks_if_not_empty(finalbody, after_block); + let else_block = blocks.append_blocks_if_not_empty(orelse, finally_block); + let try_block = blocks.append_blocks_if_not_empty(body, else_block); + + let finally_index = if finalbody.is_empty() { + None + } else { + Some(finally_block) + }; + + // If an exception is raised and not caught then terminate with exception. + let mut next_branch = blocks.create_exception_block(); + + // If there is a finally block, then re-route to finally + if let Some(finally_index) = finally_index { + blocks.blocks[next_branch].next = NextBlock::Always(finally_index); + } + + for handler in handlers.iter().rev() { + let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { + body, type_, .. + }) = handler; + let except_block = blocks.append_blocks_if_not_empty(body, finally_block); + + post_process_try( + blocks, + except_block, + None, + finally_index, + Some(finally_block), + ); + + if let Some(type_) = type_ { + let next = NextBlock::If { + condition: Condition::Except(type_.as_ref()), + next: except_block, + orelse: next_branch, + exit: after, + }; + let block = BasicBlock::new(stmts, next); + next_branch = blocks.blocks.push(block); + } else { + // If no exception type is provided, i.e., `except:` + // Then execute the body unconditionally. + next_branch = except_block; + } + } + + let except_index = if handlers.is_empty() { + None + } else { + Some(next_branch) + }; + post_process_try( + blocks, + try_block, + except_index, + finally_index, + Some(else_block), + ); + // We cannot know if the try block will raise an exception (apart from explicit raise statements) + // We therefore assume that both paths may execute + NextBlock::If { + condition: Condition::MaybeRaised, + next: next_branch, // If exception raised go to except -> except -> ... -> finally + orelse: try_block, // Otherwise try -> else -> finally + exit: after, + } +} + +/// Step through the try in the forward direction so that `assert` +/// and `raise` can be correctly directed now that the `try` and `except` +/// blocks have been established. +fn post_process_try( + blocks: &mut BasicBlocksBuilder<'_>, + start_index: BlockIndex, + except_index: Option, + finally_index: Option, + exit_index: Option, +) { + let mut idx = start_index; + let mut next_index; + + loop { + if Some(idx) == exit_index { + return; + } + + let block = &blocks.blocks[idx]; + match &block.next { + NextBlock::Always(next) => { + next_index = *next; + match block.stmts.last() { + Some(Stmt::Continue(_)) => return, + Some(Stmt::Raise(_)) => { + // re-route to except if not already re-routed + if let Some(except_index) = except_index { + if blocks.blocks[*next].is_exception() { + blocks.blocks[idx].next = NextBlock::Always(except_index); + } + } else if let Some(finally_index) = finally_index { + if blocks.blocks[*next].is_exception() { + blocks.blocks[idx].next = NextBlock::Always(finally_index); + } + } + return; + } + // return has already been re-routed + Some(Stmt::Return(_)) => return, + _ => {} + }; + } + NextBlock::If { + condition, + next, + orelse, + exit, + } => { + match block.stmts.last() { + Some(Stmt::Assert(_)) => { + next_index = *next; + // re-route to except if not already re-routed + if let Some(except_index) = except_index { + if blocks.blocks[*orelse].is_exception() { + blocks.blocks[idx].next = NextBlock::If { + condition: condition.clone(), + next: *next, + orelse: except_index, + exit: *exit, + }; + } + } else if let Some(finally_index) = finally_index { + if blocks.blocks[*orelse].is_exception() { + blocks.blocks[idx].next = NextBlock::If { + condition: condition.clone(), + next: *next, + orelse: finally_index, + exit: *exit, + }; + } + } + } + Some(Stmt::Try(_)) => { + next_index = *next; + post_process_try(blocks, *orelse, except_index, finally_index, *exit); + } + _ => { + next_index = *orelse; + post_process_try(blocks, *next, except_index, finally_index, *exit); + } + }; + } + NextBlock::Terminate => { + match block.stmts.last() { + Some(Stmt::Return(_)) => { + // re-route to finally if present and not already re-routed + if let Some(finally_index) = finally_index { + blocks.blocks[idx].next = NextBlock::Always(finally_index); + } + return; + } + _ => return, + }; + } + } + idx = next_index; + } +} + +/// Handle a single match case. +/// +/// `next_after_block` is the block *after* the entire match statement that is +/// taken after this case is taken. +/// `orelse_after_block` is the next match case (or the block after the match +/// statement if this is the last case). +fn match_case<'stmt>( + blocks: &mut BasicBlocksBuilder<'stmt>, + match_stmt: &'stmt Stmt, + subject: &'stmt Expr, + case: &'stmt MatchCase, + next_after_block: BlockIndex, + orelse_after_block: BlockIndex, +) -> BasicBlock<'stmt> { + // FIXME: this is not ideal, we want to only use the `case` statement here, + // but that is type `MatchCase`, not `Stmt`. For now we'll point to the + // entire match statement. + let stmts = std::slice::from_ref(match_stmt); + let next_block_index = blocks.append_blocks_if_not_empty(&case.body, next_after_block); + let next = if is_wildcard(case) { + // Wildcard case is always taken. + NextBlock::Always(next_block_index) + } else { + NextBlock::If { + condition: Condition::Match { subject, case }, + next: next_block_index, + orelse: orelse_after_block, + exit: Some(next_after_block), + } + }; + BasicBlock::new(stmts, next) +} + +/// Returns true if the [`MatchCase`] is a wildcard pattern. +fn is_wildcard(pattern: &MatchCase) -> bool { + /// Returns true if the [`Pattern`] is a wildcard pattern. + fn is_wildcard_pattern(pattern: &Pattern) -> bool { + match pattern { + Pattern::MatchValue(_) + | Pattern::MatchSingleton(_) + | Pattern::MatchSequence(_) + | Pattern::MatchMapping(_) + | Pattern::MatchClass(_) + | Pattern::MatchStar(_) => false, + Pattern::MatchAs(PatternMatchAs { pattern, .. }) => pattern.is_none(), + Pattern::MatchOr(PatternMatchOr { patterns, .. }) => { + patterns.iter().all(is_wildcard_pattern) + } + } + } + + pattern.guard.is_none() && is_wildcard_pattern(&pattern.pattern) +} + +#[derive(Debug, Default)] +struct BasicBlocksBuilder<'stmt> { + blocks: IndexVec>, +} + +impl<'stmt> BasicBlocksBuilder<'stmt> { + fn with_capacity(capacity: usize) -> Self { + Self { + blocks: IndexVec::with_capacity(capacity), + } + } + + /// Creates basic blocks from `stmts` and appends them to `blocks`. + fn create_blocks( + &mut self, + stmts: &'stmt [Stmt], + mut after: Option, + ) -> Option { + // We process the statements in reverse so that we can always point to the + // next block (as that should always be processed). + let mut stmts_iter = stmts.iter().enumerate().rev().peekable(); + while let Some((i, stmt)) = stmts_iter.next() { + let next = match stmt { + // Statements that continue to the next statement after execution. + Stmt::FunctionDef(_) + | Stmt::Import(_) + | Stmt::ImportFrom(_) + | Stmt::ClassDef(_) + | Stmt::Global(_) + | Stmt::Nonlocal(_) + | Stmt::Delete(_) + | Stmt::Assign(_) + | Stmt::AugAssign(_) + | Stmt::AnnAssign(_) + | Stmt::TypeAlias(_) + | Stmt::IpyEscapeCommand(_) + | Stmt::Pass(_) => self.unconditional_next_block(after), + Stmt::Break(_) | Stmt::Continue(_) => { + // NOTE: These are handled in post_process_loop. + self.unconditional_next_block(after) + } + // Statements that (can) divert the control flow. + Stmt::If(stmt_if) => { + // Always get an after_block to avoid having to get one for each branch that needs it. + let after_block = self.find_next_block_index(after); + let consequent = self.append_blocks_if_not_empty(&stmt_if.body, after_block); + + // Block ID of the next elif or else clause. + let mut next_branch = after_block; + + for clause in stmt_if.elif_else_clauses.iter().rev() { + let consequent = self.append_blocks_if_not_empty(&clause.body, after_block); + next_branch = if let Some(test) = &clause.test { + let next = NextBlock::If { + condition: Condition::Test(test), + next: consequent, + orelse: next_branch, + exit: after, + }; + let stmts = std::slice::from_ref(stmt); + let block = BasicBlock::new(stmts, next); + self.blocks.push(block) + } else { + consequent + }; + } + + NextBlock::If { + condition: Condition::Test(&stmt_if.test), + next: consequent, + orelse: next_branch, + exit: after, + } + } + Stmt::While(StmtWhile { + test: condition, + body, + orelse, + .. + }) => loop_block(self, Condition::Test(condition), body, orelse, after), + Stmt::For(StmtFor { + iter: condition, + body, + orelse, + .. + }) => loop_block(self, Condition::Iterator(condition), body, orelse, after), + Stmt::Try(_) => try_block(self, stmt, after), + Stmt::With(StmtWith { body, .. }) => { + let after_block = self.find_next_block_index(after); + let with_block = self.append_blocks(body, after); + + // The with statement is equivalent to a try statement with an except and finally block + // However, we do not have access to the except and finally. + // We therefore assume that execution may fall through on error. + NextBlock::If { + condition: Condition::MaybeRaised, + next: after_block, // If exception raised fall through + orelse: with_block, // Otherwise execute the with statement + exit: after, + } + } + Stmt::Match(StmtMatch { subject, cases, .. }) => { + let after_block = self.find_next_block_index(after); + let mut orelse_after_block = after_block; + for case in cases.iter().rev() { + let block = + match_case(self, stmt, subject, case, after_block, orelse_after_block); + // For the case above this use the just added case as the + // `orelse` branch, this convert the match statement to + // (essentially) a bunch of if statements. + orelse_after_block = self.blocks.push(block); + } + // TODO: currently we don't include the lines before the match + // statement in the block, unlike what we do for other + // statements. + after = Some(orelse_after_block); + continue; + } + Stmt::Raise(_) => { + // NOTE: This may be modified in post_process_try. + NextBlock::Always(self.create_exception_block()) + } + Stmt::Assert(stmt) => { + // NOTE: This may be modified in post_process_try. + let next = self.find_next_block_index(after); + let orelse = self.create_exception_block(); + NextBlock::If { + condition: Condition::Test(&stmt.test), + next, + orelse, + exit: after, + } + } + Stmt::Expr(stmt) => { + match &*stmt.value { + Expr::BoolOp(_) + | Expr::BinOp(_) + | Expr::UnaryOp(_) + | Expr::Dict(_) + | Expr::Set(_) + | Expr::Compare(_) + | Expr::Call(_) + | Expr::FString(_) + | Expr::StringLiteral(_) + | Expr::BytesLiteral(_) + | Expr::NumberLiteral(_) + | Expr::BooleanLiteral(_) + | Expr::NoneLiteral(_) + | Expr::EllipsisLiteral(_) + | Expr::Attribute(_) + | Expr::Subscript(_) + | Expr::Starred(_) + | Expr::Name(_) + | Expr::List(_) + | Expr::IpyEscapeCommand(_) + | Expr::Tuple(_) + | Expr::Slice(_) => self.unconditional_next_block(after), + // TODO: handle these expressions. + Expr::Named(_) + | Expr::Lambda(_) + | Expr::If(_) + | Expr::ListComp(_) + | Expr::SetComp(_) + | Expr::DictComp(_) + | Expr::Generator(_) + | Expr::Await(_) + | Expr::Yield(_) + | Expr::YieldFrom(_) => self.unconditional_next_block(after), + } + } + // The tough branches are done, here is an easy one. + Stmt::Return(_) => NextBlock::Terminate, + }; + + // Include any statements in the block that don't divert the control flow. + let mut start = i; + let end = i + 1; + while stmts_iter + .next_if(|(_, stmt)| !is_control_flow_stmt(stmt)) + .is_some() + { + start -= 1; + } + + let block = BasicBlock::new(&stmts[start..end], next); + after = Some(self.blocks.push(block)); + } + + after + } + + /// Calls [`create_blocks`] and returns this first block reached (i.e. the last + /// block). + fn append_blocks(&mut self, stmts: &'stmt [Stmt], after: Option) -> BlockIndex { + assert!(!stmts.is_empty()); + self.create_blocks(stmts, after) + .expect("Expect `create_blocks` to create a block if `stmts` is not empty") + } + + /// If `stmts` is not empty this calls [`create_blocks`] and returns this first + /// block reached (i.e. the last block). If `stmts` is empty this returns + /// `after` and doesn't change `blocks`. + fn append_blocks_if_not_empty( + &mut self, + stmts: &'stmt [Stmt], + after: BlockIndex, + ) -> BlockIndex { + if stmts.is_empty() { + after // Empty body, continue with block `after` it. + } else { + self.append_blocks(stmts, Some(after)) + } + } + + /// Select the next block from `blocks` unconditionally. + fn unconditional_next_block(&self, after: Option) -> NextBlock<'static> { + if let Some(after) = after { + return NextBlock::Always(after); + } + + // Either we continue with the next block (that is the last block `blocks`). + // Or it's the last statement, thus we terminate. + self.blocks + .last_index() + .map_or(NextBlock::Terminate, NextBlock::Always) + } + + /// Select the next block index from `blocks`. + /// If there is no next block it will add a fake/empty block. + fn find_next_block_index(&mut self, after: Option) -> BlockIndex { + if let Some(after) = after { + // Next block is already determined. + after + } else if let Some(idx) = self.blocks.last_index() { + // Otherwise we either continue with the next block (that is the last + // block in `blocks`). + idx + } else { + // Or if there are no blocks, add a fake end block. + self.blocks.push(BasicBlock::EMPTY) + } + } + + /// Returns a block index for an `EXCEPTION` block in `blocks`. + fn create_exception_block(&mut self) -> BlockIndex { + self.blocks.push(BasicBlock::EXCEPTION.clone()) + } + + /// Returns a block index for an `LOOP_CONTINUE` block in `blocks`. + fn create_loop_continue_block(&mut self) -> BlockIndex { + self.blocks.push(BasicBlock::LOOP_CONTINUE.clone()) + } + + fn finish(mut self) -> BasicBlocks<'stmt> { + if self.blocks.is_empty() { + self.blocks.push(BasicBlock::EMPTY); + } + + BasicBlocks { + blocks: self.blocks, + } + } +} + +impl<'stmt> std::ops::Deref for BasicBlocksBuilder<'stmt> { + type Target = IndexSlice>; + + fn deref(&self) -> &Self::Target { + &self.blocks + } +} + +impl std::ops::DerefMut for BasicBlocksBuilder<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.blocks + } +} + +/// Returns true if `stmt` contains a control flow statement, e.g. an `if` or +/// `return` statement. +fn is_control_flow_stmt(stmt: &Stmt) -> bool { + match stmt { + Stmt::FunctionDef(_) + | Stmt::Import(_) + | Stmt::ImportFrom(_) + | Stmt::ClassDef(_) + | Stmt::Global(_) + | Stmt::Nonlocal(_) + | Stmt::Delete(_) + | Stmt::Assign(_) + | Stmt::AugAssign(_) + | Stmt::AnnAssign(_) + | Stmt::Expr(_) + | Stmt::TypeAlias(_) + | Stmt::IpyEscapeCommand(_) + | Stmt::Pass(_) => false, + Stmt::Return(_) + | Stmt::For(_) + | Stmt::While(_) + | Stmt::If(_) + | Stmt::With(_) + | Stmt::Match(_) + | Stmt::Raise(_) + | Stmt::Try(_) + | Stmt::Assert(_) + | Stmt::Break(_) + | Stmt::Continue(_) => true, + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use std::{fmt, fs}; + + use ruff_python_parser::parse_module; + use ruff_text_size::Ranged; + use std::fmt::Write; + use test_case::test_case; + + use crate::rules::pylint::rules::unreachable::{BasicBlocks, BlockIndex, Condition, NextBlock}; + + #[test_case("simple.py")] + #[test_case("if.py")] + #[test_case("while.py")] + #[test_case("for.py")] + #[test_case("async-for.py")] + #[test_case("try.py")] + #[test_case("raise.py")] + #[test_case("assert.py")] + #[test_case("match.py")] + fn control_flow_graph(filename: &str) { + let path = PathBuf::from_iter(["resources/test/fixtures/control-flow-graph", filename]); + let source = fs::read_to_string(path).expect("failed to read file"); + let stmts = parse_module(&source) + .unwrap_or_else(|err| panic!("failed to parse source: '{source}': {err}")) + .into_suite(); + + let mut output = String::new(); + + for (i, stmts) in stmts.into_iter().enumerate() { + let Some(func) = stmts.function_def_stmt() else { + use std::io::Write; + let _ = std::io::stderr().write_all(b"unexpected statement kind, ignoring"); + continue; + }; + + let got = BasicBlocks::from(&*func.body); + // Basic sanity checks. + assert!(!got.blocks.is_empty(), "basic blocks should never be empty"); + assert_eq!( + got.blocks.first().unwrap().next, + NextBlock::Terminate, + "first block should always terminate" + ); + + let got_mermaid = MermaidGraph { + graph: &got, + source: &source, + }; + + // All block index should be valid. + let valid = BlockIndex::from_usize(got.blocks.len()); + for block in &got.blocks { + match block.next { + NextBlock::Always(index) => assert!(index < valid, "invalid block index"), + NextBlock::If { next, orelse, .. } => { + assert!(next < valid, "invalid next block index"); + assert!(orelse < valid, "invalid orelse block index"); + } + NextBlock::Terminate => {} + } + } + + writeln!( + output, + "## Function {i}\n### Source\n```python\n{}\n```\n\n### Control Flow Graph\n```mermaid\n{}```\n", + &source[func.range()], + got_mermaid + ) + .unwrap(); + } + + insta::with_settings!({ + omit_expression => true, + input_file => filename, + description => "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram." + }, { + insta::assert_snapshot!(format!("{filename}.md"), output); + }); + } + + /// Type to create a Mermaid graph. + /// + /// To learn amount Mermaid see , for the syntax + /// see . + struct MermaidGraph<'stmt, 'source> { + graph: &'stmt BasicBlocks<'stmt>, + source: &'source str, + } + + impl fmt::Display for MermaidGraph<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Flowchart type of graph, top down. + writeln!(f, "flowchart TD")?; + + // List all blocks. + writeln!(f, " start((\"Start\"))")?; + writeln!(f, " return((\"End\"))")?; + for (i, block) in self.graph.blocks.iter().enumerate() { + let (open, close) = if block.is_sentinel() { + ("[[", "]]") + } else { + ("[", "]") + }; + write!(f, " block{i}{open}\"")?; + if block.is_empty() { + write!(f, "`*(empty)*`")?; + } else if block.is_exception() { + write!(f, "Exception raised")?; + } else if block.is_loop_continue() { + write!(f, "Loop continue")?; + } else { + for stmt in block.stmts { + let code_line = &self.source[stmt.range()].trim(); + mermaid_write_quoted_str(f, code_line)?; + write!(f, "\\n")?; + } + } + writeln!(f, "\"{close}")?; + } + writeln!(f)?; + + // Then link all the blocks. + writeln!(f, " start --> block{}", self.graph.blocks.len() - 1)?; + for (i, block) in self.graph.blocks.iter_enumerated().rev() { + let i = i.as_u32(); + match &block.next { + NextBlock::Always(target) => { + writeln!(f, " block{i} --> block{target}", target = target.as_u32())?; + } + NextBlock::If { + condition, + next, + orelse, + .. + } => { + let condition_code = match condition { + Condition::MaybeRaised => "Exception raised", + _ => self.source[condition.range()].trim(), + }; + writeln!( + f, + " block{i} -- \"{condition_code}\" --> block{next}", + next = next.as_u32() + )?; + writeln!( + f, + " block{i} -- \"else\" --> block{orelse}", + orelse = orelse.as_u32() + )?; + } + NextBlock::Terminate => writeln!(f, " block{i} --> return")?, + } + } + + Ok(()) + } + } + + /// Escape double quotes (`"`) in `value` using `#quot;`. + fn mermaid_write_quoted_str(f: &mut fmt::Formatter<'_>, value: &str) -> fmt::Result { + let mut parts = value.split('"'); + if let Some(v) = parts.next() { + write!(f, "{v}")?; + } + for v in parts { + write!(f, "#quot;{v}")?; + } + Ok(()) + } +} diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0101_unreachable.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0101_unreachable.py.snap new file mode 100644 index 0000000000000..201782bab9e57 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0101_unreachable.py.snap @@ -0,0 +1,270 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +unreachable.py:3:5: PLW0101 Unreachable code in `after_return` + | +1 | def after_return(): +2 | return "reachable" +3 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +4 | +5 | async def also_works_on_async_functions(): + | + +unreachable.py:7:5: PLW0101 Unreachable code in `also_works_on_async_functions` + | +5 | async def also_works_on_async_functions(): +6 | return "reachable" +7 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +8 | +9 | def if_always_true(): + | + +unreachable.py:12:5: PLW0101 Unreachable code in `if_always_true` + | +10 | if True: +11 | return "reachable" +12 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +13 | +14 | def if_always_false(): + | + +unreachable.py:16:9: PLW0101 Unreachable code in `if_always_false` + | +14 | def if_always_false(): +15 | if False: +16 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +17 | return "reachable" + | + +unreachable.py:21:9: PLW0101 Unreachable code in `if_elif_always_false` + | +19 | def if_elif_always_false(): +20 | if False: +21 | return "unreachable" + | _________^ +22 | | elif False: +23 | | return "also unreachable" + | |_________________________________^ PLW0101 +24 | return "reachable" + | + +unreachable.py:28:9: PLW0101 Unreachable code in `if_elif_always_true` + | +26 | def if_elif_always_true(): +27 | if False: +28 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +29 | elif True: +30 | return "reachable" + | + +unreachable.py:31:5: PLW0101 Unreachable code in `if_elif_always_true` + | +29 | elif True: +30 | return "reachable" +31 | return "also unreachable" + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0101 +32 | +33 | def ends_with_if(): + | + +unreachable.py:35:9: PLW0101 Unreachable code in `ends_with_if` + | +33 | def ends_with_if(): +34 | if False: +35 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +36 | else: +37 | return "reachable" + | + +unreachable.py:42:5: PLW0101 Unreachable code in `infinite_loop` + | +40 | while True: +41 | continue +42 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +43 | +44 | ''' TODO: we could determine these, but we don't yet. + | + +unreachable.py:75:5: PLW0101 Unreachable code in `match_wildcard` + | +73 | case _: +74 | return "reachable" +75 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +76 | +77 | def match_case_and_wildcard(status): + | + +unreachable.py:83:5: PLW0101 Unreachable code in `match_case_and_wildcard` + | +81 | case _: +82 | return "reachable" +83 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +84 | +85 | def raise_exception(): + | + +unreachable.py:87:5: PLW0101 Unreachable code in `raise_exception` + | +85 | def raise_exception(): +86 | raise Exception +87 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +88 | +89 | def while_false(): + | + +unreachable.py:91:9: PLW0101 Unreachable code in `while_false` + | +89 | def while_false(): +90 | while False: +91 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +92 | return "reachable" + | + +unreachable.py:96:9: PLW0101 Unreachable code in `while_false_else` + | +94 | def while_false_else(): +95 | while False: +96 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +97 | else: +98 | return "reachable" + | + +unreachable.py:102:9: PLW0101 Unreachable code in `while_false_else_return` + | +100 | def while_false_else_return(): +101 | while False: +102 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +103 | else: +104 | return "reachable" + | + +unreachable.py:105:5: PLW0101 Unreachable code in `while_false_else_return` + | +103 | else: +104 | return "reachable" +105 | return "also unreachable" + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0101 +106 | +107 | def while_true(): + | + +unreachable.py:110:5: PLW0101 Unreachable code in `while_true` + | +108 | while True: +109 | return "reachable" +110 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +111 | +112 | def while_true_else(): + | + +unreachable.py:116:9: PLW0101 Unreachable code in `while_true_else` + | +114 | return "reachable" +115 | else: +116 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +117 | +118 | def while_true_else_return(): + | + +unreachable.py:122:9: PLW0101 Unreachable code in `while_true_else_return` + | +120 | return "reachable" +121 | else: +122 | return "unreachable" + | _________^ +123 | | return "also unreachable" + | |_____________________________^ PLW0101 +124 | +125 | def while_false_var_i(): + | + +unreachable.py:128:9: PLW0101 Unreachable code in `while_false_var_i` + | +126 | i = 0 +127 | while False: +128 | i += 1 + | ^^^^^^ PLW0101 +129 | return i + | + +unreachable.py:135:5: PLW0101 Unreachable code in `while_true_var_i` + | +133 | while True: +134 | i += 1 +135 | return i + | ^^^^^^^^ PLW0101 +136 | +137 | def while_infinite(): + | + +unreachable.py:140:5: PLW0101 Unreachable code in `while_infinite` + | +138 | while True: +139 | pass +140 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +141 | +142 | def while_if_true(): + | + +unreachable.py:146:5: PLW0101 Unreachable code in `while_if_true` + | +144 | if True: +145 | return "reachable" +146 | return "unreachable" + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +147 | +148 | def while_break(): + | + +unreachable.py:152:9: PLW0101 Unreachable code in `while_break` + | +150 | print("reachable") +151 | break +152 | print("unreachable") + | ^^^^^^^^^^^^^^^^^^^^ PLW0101 +153 | return "reachable" + | + +unreachable.py:248:5: PLW0101 Unreachable code in `after_return` + | +246 | def after_return(): +247 | return "reachable" +248 | print("unreachable") + | _____^ +249 | | print("unreachable") +250 | | print("unreachable") +251 | | print("unreachable") +252 | | print("unreachable") + | |________________________^ PLW0101 + | + +unreachable.py:257:5: PLW0101 Unreachable code in `check_if_url_exists` + | +255 | def check_if_url_exists(url: str) -> bool: # type: ignore[return] +256 | return True # uncomment to check URLs +257 | response = requests.head(url, allow_redirects=True) + | _____^ +258 | | if response.status_code == 200: +259 | | return True +260 | | if response.status_code == 404: +261 | | return False +262 | | console.print(f"[red]Unexpected error received: {response.status_code}[/]") +263 | | response.raise_for_status() + | |_______________________________^ PLW0101 + | diff --git a/ruff.schema.json b/ruff.schema.json index ee7380fe88a68..3c2fdb5e2d87a 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3607,6 +3607,7 @@ "PLW0", "PLW01", "PLW010", + "PLW0101", "PLW0108", "PLW012", "PLW0120",