From aa93005d8d203fa867050a54f224c76f72351fe5 Mon Sep 17 00:00:00 2001 From: Dylan Date: Tue, 1 Apr 2025 05:53:42 -0500 Subject: [PATCH] Control flow graph: setup (#17064) This PR contains the scaffolding for a new control flow graph implementation, along with its application to the `unreachable` rule. At the moment, the implementation is a maximal over-approximation: no control flow is modeled and all statements are counted as reachable. With each additional statement type we support, this approximation will improve. So this PR just contains: - A `ControlFlowGraph` struct and builder - Support for printing the flow graph as a Mermaid graph - Snapshot tests for the actual graphs - (a very bad!) reimplementation of `unreachable` using the new structs - Snapshot tests for `unreachable` # Instructions for Viewing Mermaid snapshots Unfortunately I don't know how to convince GitHub to render the Mermaid graphs in the snapshots. However, you can view these locally in VSCode if you install an extension that supports Mermaid graphs in Markdown, and then add this to your `settings.json`: ```json "files.associations": { "*.md.snap": "markdown", } ``` --- Cargo.lock | 3 +- crates/ruff_linter/Cargo.toml | 1 - .../test/fixtures/pylint/unreachable.py | 271 +--- .../src/checkers/ast/analyze/statement.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 --- ...ts__try-finally-nested-if-while.py.md.snap | 63 - ..._rules__unreachable__tests__try.py.md.snap | 783 ----------- ...ules__unreachable__tests__while.py.md.snap | 839 ------------ .../src/rules/pylint/rules/unreachable.rs | 1213 +---------------- ...pylint__tests__PLW0101_unreachable.py.snap | 262 ---- crates/ruff_python_semantic/Cargo.toml | 2 + .../resources/test/fixtures/cfg/no_flow.py | 24 + crates/ruff_python_semantic/src/cfg/graph.rs | 293 ++++ crates/ruff_python_semantic/src/cfg/mod.rs | 61 + ...n_semantic__cfg__tests__no_flow.py.md.snap | 89 ++ .../ruff_python_semantic/src/cfg/visualize.rs | 244 ++++ crates/ruff_python_semantic/src/lib.rs | 1 + pyproject.toml | 3 +- 24 files changed, 775 insertions(+), 6244 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__assert.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__async-for.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__for.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__if.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__match.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__raise.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__simple.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try-finally-nested-if-while.py.md.snap delete mode 100644 crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try.py.md.snap delete 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_python_semantic/resources/test/fixtures/cfg/no_flow.py create mode 100644 crates/ruff_python_semantic/src/cfg/graph.rs create mode 100644 crates/ruff_python_semantic/src/cfg/mod.rs create mode 100644 crates/ruff_python_semantic/src/cfg/snapshots/ruff_python_semantic__cfg__tests__no_flow.py.md.snap create mode 100644 crates/ruff_python_semantic/src/cfg/visualize.rs diff --git a/Cargo.lock b/Cargo.lock index f0e54ece63..64ef7eb620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3010,7 +3010,6 @@ dependencies = [ "ruff_annotate_snippets", "ruff_cache", "ruff_diagnostics", - "ruff_index", "ruff_macros", "ruff_notebook", "ruff_python_ast", @@ -3205,6 +3204,7 @@ name = "ruff_python_semantic" version = "0.0.0" dependencies = [ "bitflags 2.9.0", + "insta", "is-macro", "ruff_cache", "ruff_index", @@ -3217,6 +3217,7 @@ dependencies = [ "schemars", "serde", "smallvec", + "test-case", ] [[package]] diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 17da39d00e..4962bf5981 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -16,7 +16,6 @@ license = { workspace = true } ruff_annotate_snippets = { workspace = true } 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/pylint/unreachable.py b/crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py index a0079060f6..4a543dfd14 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py @@ -1,263 +1,14 @@ -def after_return(): - return "reachable" - return "unreachable" +def empty_statement_reachable(): ... -async def also_works_on_async_functions(): - return "reachable" - return "unreachable" +def pass_statement_reachable(): + pass -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) +def no_control_flow_reachable(): + x = 1 + x = 2 + class C: + a = 2 + c = C() + del c + def foo(): 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 3d770544d0..e378e070d6 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -349,7 +349,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } #[cfg(any(feature = "test-rules", test))] if checker.enabled(Rule::UnreachableCode) { - checker.report_diagnostics(pylint::rules::in_function(name, body)); + pylint::rules::in_function(checker, name, body); } if checker.enabled(Rule::ReimplementedOperator) { refurb::rules::reimplemented_operator(checker, &function_def.into()); 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 deleted file mode 100644 index 8c64a425ab..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__assert.py.md.snap +++ /dev/null @@ -1,244 +0,0 @@ ---- -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 deleted file mode 100644 index 327495f2db..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__async-for.py.md.snap +++ /dev/null @@ -1,257 +0,0 @@ ---- -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 deleted file mode 100644 index 71cd265b15..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__for.py.md.snap +++ /dev/null @@ -1,590 +0,0 @@ ---- -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 deleted file mode 100644 index 7f158e62c3..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__if.py.md.snap +++ /dev/null @@ -1,720 +0,0 @@ ---- -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 deleted file mode 100644 index a91f351cd0..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__match.py.md.snap +++ /dev/null @@ -1,823 +0,0 @@ ---- -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 deleted file mode 100644 index 3f3c1c3ceb..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__raise.py.md.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -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 deleted file mode 100644 index 8441684bbe..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__simple.py.md.snap +++ /dev/null @@ -1,188 +0,0 @@ ---- -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-finally-nested-if-while.py.md.snap b/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try-finally-nested-if-while.py.md.snap deleted file mode 100644 index 73ff72848b..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try-finally-nested-if-while.py.md.snap +++ /dev/null @@ -1,63 +0,0 @@ ---- -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 l(): - while T: - try: - while (): - if 3: - break - finally: - return -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Loop continue"]] - block2["return\n"] - block3[["Loop continue"]] - block4["break\n"] - block5["if 3: - break\n"] - block6["while (): - if 3: - break\n"] - block7[["Exception raised"]] - block8["try: - while (): - if 3: - break - finally: - return\n"] - block9["while T: - try: - while (): - if 3: - break - finally: - return\n"] - - start --> block9 - block9 -- "T" --> block8 - block9 -- "else" --> block0 - block8 -- "Exception raised" --> block7 - block8 -- "else" --> block6 - block7 --> block2 - block6 -- "()" --> block5 - block6 -- "else" --> block2 - block5 -- "3" --> block4 - block5 -- "else" --> block3 - block4 --> block2 - block3 --> block6 - block2 --> return - block1 --> block9 - 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 deleted file mode 100644 index 9d38653a79..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__try.py.md.snap +++ /dev/null @@ -1,783 +0,0 @@ ---- -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 -``` - -## Function 13 -### Source -```python -def func(): - for i in(): - try: - try: - while r: - if t:break - finally:() - return - except:l -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Loop continue"]] - block2["return\n"] - block3["()\n"] - block4[["Loop continue"]] - block5["break\n"] - block6["if t:break\n"] - block7["while r: - if t:break\n"] - block8[["Exception raised"]] - block9["try: - while r: - if t:break - finally:()\n"] - block10[["Exception raised"]] - block11["l\n"] - block12["try: - try: - while r: - if t:break - finally:() - return - except:l\n"] - block13["for i in(): - try: - try: - while r: - if t:break - finally:() - return - except:l\n"] - - start --> block13 - block13 -- "()" --> block12 - block13 -- "else" --> block0 - block12 -- "Exception raised" --> block11 - block12 -- "else" --> block9 - block11 --> block1 - block10 --> return - block9 -- "Exception raised" --> block8 - block9 -- "else" --> block7 - block8 --> block3 - block7 -- "r" --> block6 - block7 -- "else" --> block3 - block6 -- "t" --> block5 - block6 -- "else" --> block4 - block5 --> block3 - block4 --> block7 - block3 --> block2 - block2 --> return - block1 --> block13 - 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 deleted file mode 100644 index 9ea1454a70..0000000000 --- a/crates/ruff_linter/src/rules/pylint/rules/snapshots/ruff_linter__rules__pylint__rules__unreachable__tests__while.py.md.snap +++ /dev/null @@ -1,839 +0,0 @@ ---- -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 -``` - -## Function 23 -### Source -```python -def func(): - while T: - try: - while(): - if 3: - break - finally: - return -``` - -### Control Flow Graph -```mermaid -flowchart TD - start(("Start")) - return(("End")) - block0[["`*(empty)*`"]] - block1[["Loop continue"]] - block2["return\n"] - block3[["Loop continue"]] - block4["break\n"] - block5["if 3: - break\n"] - block6["while(): - if 3: - break\n"] - block7[["Exception raised"]] - block8["try: - while(): - if 3: - break - finally: - return\n"] - block9["while T: - try: - while(): - if 3: - break - finally: - return\n"] - - start --> block9 - block9 -- "T" --> block8 - block9 -- "else" --> block0 - block8 -- "Exception raised" --> block7 - block8 -- "else" --> block6 - block7 --> block2 - block6 -- "()" --> block5 - block6 -- "else" --> block2 - block5 -- "3" --> block4 - block5 -- "else" --> block3 - block4 --> block2 - block3 --> block6 - block2 --> return - block1 --> block9 - block0 --> return -``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs index c6beb2e67a..892a958499 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs @@ -1,15 +1,14 @@ -use std::cmp; +use std::collections::HashSet; -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 itertools::Itertools; +use ruff_python_ast::{Identifier, Stmt}; +use ruff_python_semantic::cfg::graph::{build_cfg, BlockId, Condition, ControlFlowGraph}; +use ruff_text_size::{Ranged, TextRange}; use ruff_diagnostics::{Diagnostic, Violation}; -use ruff_index::{IndexSlice, IndexVec}; -use ruff_macros::{derive_message_formats, newtype_index, ViolationMetadata}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; + +use crate::checkers::ast::Checker; /// ## What it does /// Checks for unreachable code. @@ -43,1182 +42,60 @@ impl Violation for UnreachableCode { } } -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); - } +pub(crate) fn in_function(checker: &Checker, name: &Identifier, body: &[Stmt]) { + let cfg = build_cfg(body); + let reachable = reachable(&cfg); - let mut diagnostics: Vec = Vec::new(); + let mut unreachable = (0..cfg.num_blocks()) + .map(BlockId::from_usize) + .filter(|block| !reachable.contains(block) && !cfg.stmts(*block).is_empty()) + .map(|block| cfg.range(block)) + .sorted_by_key(ruff_text_size::Ranged::start) + .peekable(); - // 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()); - } + while let Some(block_range) = unreachable.next() { + let start = block_range.start(); + let mut end = block_range.end(); + while let Some(next_block) = unreachable.next_if(|nxt| nxt.start() <= end) { + end = next_block.end(); } + checker.report_diagnostic(Diagnostic::new( + UnreachableCode { + name: name.to_string(), + }, + TextRange::new(start, 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; +/// Returns set of block indices reachable from entry block +fn reachable(cfg: &ControlFlowGraph) -> HashSet { + let mut reachable = HashSet::with_capacity(cfg.num_blocks()); + let mut stack = Vec::new(); - loop { - if blocks[idx].reachable { - return; // Block already visited, no needed to do it again. - } - blocks[idx].reachable = true; + stack.push(cfg.initial()); - 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, + while let Some(block) = stack.pop() { + if reachable.insert(block) { + stack.extend( + cfg.outgoing(block) + // Traverse edges that are statically known to be possible to cross. + .filter_targets_by_conditions(|cond| matches!(taken(cond), Some(true) | None)), + ); } } + + reachable } /// 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`. +#[allow(clippy::unnecessary_wraps)] 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); - } - Some(Stmt::Return(_)) => return, - _ => {} - }; - 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::Break(_)) => return, - 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(_)) => { - // if we are already in a `finally` block, terminate - if Some(idx) == finally_index { - 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")] - #[test_case("try-finally-nested-if-while.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(()) + Condition::Always => Some(true), } } 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 index 0a50697022..6c123427ab 100644 --- 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 @@ -1,266 +1,4 @@ --- 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/crates/ruff_python_semantic/Cargo.toml b/crates/ruff_python_semantic/Cargo.toml index 70109ded87..8005ec824f 100644 --- a/crates/ruff_python_semantic/Cargo.toml +++ b/crates/ruff_python_semantic/Cargo.toml @@ -27,6 +27,8 @@ serde = { workspace = true, optional = true } smallvec = { workspace = true } [dev-dependencies] +insta = { workspace = true, features = ["filters", "json", "redactions"] } +test-case = { workspace = true } ruff_python_parser = { workspace = true } [lints] diff --git a/crates/ruff_python_semantic/resources/test/fixtures/cfg/no_flow.py b/crates/ruff_python_semantic/resources/test/fixtures/cfg/no_flow.py new file mode 100644 index 0000000000..1d97344455 --- /dev/null +++ b/crates/ruff_python_semantic/resources/test/fixtures/cfg/no_flow.py @@ -0,0 +1,24 @@ +def func(): ... + + +def func(): + pass + + +def func(): + x = 1 + x = 2 + + +def func(): + foo() + + +def func(): + from foo import bar + + class C: + a = 1 + + c = C() + del c diff --git a/crates/ruff_python_semantic/src/cfg/graph.rs b/crates/ruff_python_semantic/src/cfg/graph.rs new file mode 100644 index 0000000000..b40d42b560 --- /dev/null +++ b/crates/ruff_python_semantic/src/cfg/graph.rs @@ -0,0 +1,293 @@ +use ruff_index::{newtype_index, IndexVec}; +use ruff_python_ast::Stmt; +use ruff_text_size::{Ranged, TextRange}; +use smallvec::{smallvec, SmallVec}; + +/// Returns the control flow graph associated to an array of statements +pub fn build_cfg(stmts: &[Stmt]) -> ControlFlowGraph<'_> { + let mut builder = CFGBuilder::with_capacity(stmts.len()); + builder.process_stmts(stmts); + builder.finish() +} + +/// Control flow graph +#[derive(Debug)] +pub struct ControlFlowGraph<'stmt> { + /// Basic blocks - the nodes of the control flow graph + blocks: IndexVec>, + /// Entry point to the control flow graph + initial: BlockId, + /// Terminal block - will always be empty + terminal: BlockId, +} + +impl<'stmt> ControlFlowGraph<'stmt> { + /// Index of entry point to the control flow graph + pub fn initial(&self) -> BlockId { + self.initial + } + + /// Index of terminal block + pub fn terminal(&self) -> BlockId { + self.terminal + } + + /// Number of basic blocks, or nodes, in the graph + pub fn num_blocks(&self) -> usize { + self.blocks.len() + } + + /// Returns the statements comprising the basic block at the given index + pub fn stmts(&self, block: BlockId) -> &'stmt [Stmt] { + self.blocks[block].stmts + } + + /// Returns the range of the statements comprising the basic block at the given index + pub fn range(&self, block: BlockId) -> TextRange { + self.blocks[block].range() + } + + /// Returns the [`Edges`] going out of the basic block at the given index + pub fn outgoing(&self, block: BlockId) -> &Edges { + &self.blocks[block].out + } + + /// Returns an iterator over the indices of the direct predecessors of the block at the given index + pub fn predecessors(&self, block: BlockId) -> impl ExactSizeIterator + '_ { + self.blocks[block].parents.iter().copied() + } + + /// Returns the [`BlockKind`] of the block at the given index + pub(crate) fn kind(&self, block: BlockId) -> BlockKind { + self.blocks[block].kind + } +} + +#[newtype_index] +pub struct BlockId; + +/// Holds the data of a basic block. A basic block consists of a collection of +/// [`Stmt`]s, together with outgoing edges to other basic blocks. +#[derive(Debug, Default)] +struct BlockData<'stmt> { + kind: BlockKind, + /// Slice of statements regarded as executing unconditionally in order + stmts: &'stmt [Stmt], + /// Outgoing edges, indicating possible paths of execution after the + /// block has concluded + out: Edges, + /// Collection of indices for basic blocks having the current + /// block as the target of an edge + parents: SmallVec<[BlockId; 2]>, +} + +impl Ranged for BlockData<'_> { + fn range(&self) -> TextRange { + let Some(first) = self.stmts.first() else { + return TextRange::default(); + }; + let Some(last) = self.stmts.last() else { + return TextRange::default(); + }; + + TextRange::new(first.start(), last.end()) + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub(crate) enum BlockKind { + #[default] + Generic, + /// Entry point of the control flow graph + Start, + /// Terminal block for the control flow graph + Terminal, +} + +/// Holds a collection of edges. Each edge is determined by: +/// - a [`Condition`] for traversing the edge, and +/// - a target block, specified by its [`BlockId`]. +/// +/// The conditions and targets are kept in two separate +/// vectors which must always be kept the same length. +#[derive(Debug, Default, Clone)] +pub struct Edges { + conditions: SmallVec<[Condition; 4]>, + targets: SmallVec<[BlockId; 4]>, +} + +impl Edges { + /// Creates an unconditional edge to the target block + fn always(target: BlockId) -> Self { + Self { + conditions: smallvec![Condition::Always], + targets: smallvec![target], + } + } + + /// Returns iterator over indices of blocks targeted by given edges + pub fn targets(&self) -> impl ExactSizeIterator + '_ { + self.targets.iter().copied() + } + + /// Returns iterator over [`Condition`]s which must be satisfied to traverse corresponding edge + pub fn conditions(&self) -> impl ExactSizeIterator { + self.conditions.iter() + } + + fn is_empty(&self) -> bool { + self.targets.is_empty() + } + + pub fn filter_targets_by_conditions<'a, T: FnMut(&Condition) -> bool + 'a>( + &'a self, + mut predicate: T, + ) -> impl Iterator + 'a { + self.conditions() + .zip(self.targets()) + .filter(move |(cond, _)| predicate(cond)) + .map(|(_, block)| block) + } +} + +/// Represents a condition to be tested in a multi-way branch +#[derive(Debug, Clone)] +pub enum Condition { + /// Unconditional edge + Always, +} + +struct CFGBuilder<'stmt> { + /// Control flow graph under construction + cfg: ControlFlowGraph<'stmt>, + /// Current basic block index + current: BlockId, + /// Exit block index for current control flow + exit: BlockId, +} + +impl<'stmt> CFGBuilder<'stmt> { + /// Returns [`CFGBuilder`] with vector of blocks initialized at given capacity and with both initial and terminal blocks populated. + fn with_capacity(capacity: usize) -> Self { + let mut blocks = IndexVec::with_capacity(capacity); + let initial = blocks.push(BlockData { + kind: BlockKind::Start, + ..BlockData::default() + }); + let terminal = blocks.push(BlockData { + kind: BlockKind::Terminal, + ..BlockData::default() + }); + + Self { + cfg: ControlFlowGraph { + blocks, + initial, + terminal, + }, + current: initial, + exit: terminal, + } + } + + /// Runs the core logic for the builder. + fn process_stmts(&mut self, stmts: &'stmt [Stmt]) { + let start = 0; + for stmt in stmts { + let cache_exit = self.exit(); + match stmt { + Stmt::FunctionDef(_) + | Stmt::ClassDef(_) + | Stmt::Assign(_) + | Stmt::AugAssign(_) + | Stmt::AnnAssign(_) + | Stmt::TypeAlias(_) + | Stmt::Import(_) + | Stmt::ImportFrom(_) + | Stmt::Global(_) + | Stmt::Nonlocal(_) + | Stmt::Expr(_) + | Stmt::Pass(_) + | Stmt::Delete(_) + | Stmt::IpyEscapeCommand(_) => {} + // Loops + Stmt::While(_) => {} + Stmt::For(_) => {} + + // Switch statements + Stmt::If(_) => {} + Stmt::Match(_) => {} + + // Exception handling statements + Stmt::Try(_) => {} + Stmt::With(_) => {} + + // Jumps + Stmt::Return(_) => {} + Stmt::Break(_) => {} + Stmt::Continue(_) => {} + Stmt::Raise(_) => {} + + // An `assert` is a mixture of a switch and a jump. + Stmt::Assert(_) => {} + } + // Restore exit + self.update_exit(cache_exit); + } + // It can happen that we have statements left over + // and not yet occupying a block. In that case, + // `self.current` should be pointing to an empty block + // and we push the remaining statements to it here. + if start < stmts.len() { + self.set_current_block_stmts(&stmts[start..]); + } + // Add edge to exit if not already present + if self.cfg.blocks[self.current].out.is_empty() { + let edges = Edges::always(self.exit()); + self.set_current_block_edges(edges); + } + self.move_to(self.exit()); + } + + /// Returns finished control flow graph + fn finish(self) -> ControlFlowGraph<'stmt> { + self.cfg + } + + /// Current exit block, which may change during construction + fn exit(&self) -> BlockId { + self.exit + } + + /// Point the current exit to block at provided index + fn update_exit(&mut self, new_exit: BlockId) { + self.exit = new_exit; + } + + /// Moves current block to provided index + fn move_to(&mut self, block: BlockId) { + self.current = block; + } + + /// Populates the current basic block with the given set of statements. + /// + /// This should only be called once on any given block. + fn set_current_block_stmts(&mut self, stmts: &'stmt [Stmt]) { + debug_assert!( + self.cfg.blocks[self.current].stmts.is_empty(), + "Attempting to set statements on an already populated basic block." + ); + self.cfg.blocks[self.current].stmts = stmts; + } + + /// Draws provided edges out of the current basic block. + /// + /// This should only be called once on any given block. + fn set_current_block_edges(&mut self, edges: Edges) { + debug_assert!( + self.cfg.blocks[self.current].out.is_empty(), + "Attempting to set edges on a basic block that already has an outgoing edge." + ); + self.cfg.blocks[self.current].out = edges; + } +} diff --git a/crates/ruff_python_semantic/src/cfg/mod.rs b/crates/ruff_python_semantic/src/cfg/mod.rs new file mode 100644 index 0000000000..b289e07d20 --- /dev/null +++ b/crates/ruff_python_semantic/src/cfg/mod.rs @@ -0,0 +1,61 @@ +pub mod graph; +pub mod visualize; + +#[cfg(test)] +mod tests { + use std::fmt::Write; + use std::fs; + use std::path::PathBuf; + + use crate::cfg::graph::build_cfg; + use crate::cfg::visualize::draw_cfg; + use insta; + + use ruff_python_parser::parse_module; + use ruff_text_size::Ranged; + use test_case::test_case; + + #[test_case("no_flow.py")] + fn control_flow_graph(filename: &str) { + let path = PathBuf::from("resources/test/fixtures/cfg").join(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, stmt) in stmts.into_iter().enumerate() { + let func = stmt.as_function_def_stmt().expect( + "Snapshot test for control flow graph should consist only of function definitions", + ); + let cfg = build_cfg(&func.body); + + let mermaid_graph = draw_cfg(cfg, &source); + writeln!( + output, + "## Function {}\n\ + ### Source\n\ + ```python\n\ + {}\n\ + ```\n\n\ + ### Control Flow Graph\n\ + ```mermaid\n\ + {}\n\ + ```\n", + i, + &source[func.range()], + mermaid_graph, + ) + .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); + }); + } +} diff --git a/crates/ruff_python_semantic/src/cfg/snapshots/ruff_python_semantic__cfg__tests__no_flow.py.md.snap b/crates/ruff_python_semantic/src/cfg/snapshots/ruff_python_semantic__cfg__tests__no_flow.py.md.snap new file mode 100644 index 0000000000..29c03fc310 --- /dev/null +++ b/crates/ruff_python_semantic/src/cfg/snapshots/ruff_python_semantic__cfg__tests__no_flow.py.md.snap @@ -0,0 +1,89 @@ +--- +source: crates/ruff_python_semantic/src/cfg/mod.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(): ... +``` + +### Control Flow Graph +```mermaid +flowchart TD + node0["..."] + node1((("EXIT"))) + node0==>node1 +``` + +## Function 1 +### Source +```python +def func(): + pass +``` + +### Control Flow Graph +```mermaid +flowchart TD + node0["pass"] + node1((("EXIT"))) + node0==>node1 +``` + +## Function 2 +### Source +```python +def func(): + x = 1 + x = 2 +``` + +### Control Flow Graph +```mermaid +flowchart TD + node0["x = 1 +x = 2"] + node1((("EXIT"))) + node0==>node1 +``` + +## Function 3 +### Source +```python +def func(): + foo() +``` + +### Control Flow Graph +```mermaid +flowchart TD + node0["foo()"] + node1((("EXIT"))) + node0==>node1 +``` + +## Function 4 +### Source +```python +def func(): + from foo import bar + + class C: + a = 1 + + c = C() + del c +``` + +### Control Flow Graph +```mermaid +flowchart TD + node0["from foo import bar +class C: + a = 1 +c = C() +del c"] + node1((("EXIT"))) + node0==>node1 +``` diff --git a/crates/ruff_python_semantic/src/cfg/visualize.rs b/crates/ruff_python_semantic/src/cfg/visualize.rs new file mode 100644 index 0000000000..4e09729140 --- /dev/null +++ b/crates/ruff_python_semantic/src/cfg/visualize.rs @@ -0,0 +1,244 @@ +//! Heavily inspired by rustc data structures +use ruff_index::Idx; +use ruff_text_size::Ranged; +use std::fmt::{self, Display}; + +use crate::cfg::graph::{BlockId, BlockKind, Condition, ControlFlowGraph}; + +/// Returns control flow graph in Mermaid syntax. +pub fn draw_cfg(graph: ControlFlowGraph, source: &str) -> String { + CFGWithSource::new(graph, source).draw_graph() +} + +trait MermaidGraph<'a>: DirectedGraph<'a> { + fn draw_node(&self, node: Self::Node) -> MermaidNode; + fn draw_edges(&self, node: Self::Node) -> impl Iterator; + + fn draw_graph(&self) -> String { + let mut graph = Vec::new(); + + // Begin mermaid graph. + graph.push("flowchart TD".to_string()); + + // Draw nodes + let num_nodes = self.num_nodes(); + for idx in 0..num_nodes { + let node = Self::Node::new(idx); + graph.push(format!("\tnode{}{}", idx, &self.draw_node(node))); + } + + // Draw edges + for idx in 0..num_nodes { + graph.extend( + self.draw_edges(Self::Node::new(idx)) + .map(|(end_idx, edge)| format!("\tnode{}{}node{}", idx, edge, end_idx.index())), + ); + } + graph.join("\n") + } +} + +pub struct MermaidNode { + shape: MermaidNodeShape, + content: String, +} + +impl MermaidNode { + pub fn with_content(content: String) -> Self { + Self { + shape: MermaidNodeShape::default(), + content, + } + } + + 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(()) + } +} + +impl Display for MermaidNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (open, close) = self.shape.open_close(); + write!(f, "{open}\"")?; + if self.content.is_empty() { + write!(f, "empty")?; + } else { + MermaidNode::mermaid_write_quoted_str(f, &self.content)?; + } + write!(f, "\"{close}") + } +} + +#[derive(Debug, Default)] +pub enum MermaidNodeShape { + #[default] + Rectangle, + DoubleRectangle, + RoundedRectangle, + Stadium, + Circle, + DoubleCircle, + Asymmetric, + Rhombus, + Hexagon, + Parallelogram, + Trapezoid, +} + +impl MermaidNodeShape { + fn open_close(&self) -> (&'static str, &'static str) { + match self { + Self::Rectangle => ("[", "]"), + Self::DoubleRectangle => ("[[", "]]"), + Self::RoundedRectangle => ("(", ")"), + Self::Stadium => ("([", "])"), + Self::Circle => ("((", "))"), + Self::DoubleCircle => ("(((", ")))"), + Self::Asymmetric => (">", "]"), + Self::Rhombus => ("{", "}"), + Self::Hexagon => ("{{", "}}"), + Self::Parallelogram => ("[/", "/]"), + Self::Trapezoid => ("[/", "\\]"), + } + } +} + +#[derive(Debug, Default)] +pub struct MermaidEdge { + kind: MermaidEdgeKind, + content: String, +} + +impl Display for MermaidEdge { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.content.is_empty() { + write!(f, "{}", self.kind) + } else { + write!(f, "{}|\"{}\"|", self.kind, self.content) + } + } +} + +#[derive(Debug, Default)] +pub enum MermaidEdgeKind { + #[default] + Arrow, + DottedArrow, + ThickArrow, + BidirectionalArrow, +} + +impl Display for MermaidEdgeKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MermaidEdgeKind::Arrow => write!(f, "-->"), + MermaidEdgeKind::DottedArrow => write!(f, "-..->"), + MermaidEdgeKind::ThickArrow => write!(f, "==>"), + MermaidEdgeKind::BidirectionalArrow => write!(f, "<-->"), + } + } +} + +pub trait DirectedGraph<'a> { + type Node: Idx; + + fn num_nodes(&self) -> usize; + fn start_node(&self) -> Self::Node; + fn successors(&self, node: Self::Node) -> impl ExactSizeIterator + '_; +} + +struct CFGWithSource<'stmt> { + cfg: ControlFlowGraph<'stmt>, + source: &'stmt str, +} + +impl<'stmt> CFGWithSource<'stmt> { + fn new(cfg: ControlFlowGraph<'stmt>, source: &'stmt str) -> Self { + Self { cfg, source } + } +} + +impl<'stmt> DirectedGraph<'stmt> for CFGWithSource<'stmt> { + type Node = BlockId; + + fn num_nodes(&self) -> usize { + self.cfg.num_blocks() + } + + fn start_node(&self) -> Self::Node { + self.cfg.initial() + } + + fn successors(&self, node: Self::Node) -> impl ExactSizeIterator + '_ { + self.cfg.outgoing(node).targets() + } +} + +impl<'stmt> MermaidGraph<'stmt> for CFGWithSource<'stmt> { + fn draw_node(&self, node: Self::Node) -> MermaidNode { + let statements: Vec = self + .cfg + .stmts(node) + .iter() + .map(|stmt| self.source[stmt.range()].to_string()) + .collect(); + let content = match self.cfg.kind(node) { + BlockKind::Generic => { + if statements.is_empty() { + "EMPTY".to_string() + } else { + statements.join("\n") + } + } + BlockKind::Start => { + if statements.is_empty() { + "START".to_string() + } else { + statements.join("\n") + } + } + BlockKind::Terminal => { + return MermaidNode { + content: "EXIT".to_string(), + shape: MermaidNodeShape::DoubleCircle, + } + } + }; + + MermaidNode::with_content(content) + } + + fn draw_edges(&self, node: Self::Node) -> impl Iterator { + let edge_data = self.cfg.outgoing(node); + edge_data + .targets() + .zip(edge_data.conditions()) + .map(|(target, condition)| { + let edge = match condition { + Condition::Always => { + if target == self.cfg.terminal() { + MermaidEdge { + kind: MermaidEdgeKind::ThickArrow, + content: String::new(), + } + } else { + MermaidEdge { + kind: MermaidEdgeKind::Arrow, + content: String::new(), + } + } + } + }; + (target, edge) + }) + .collect::>() + .into_iter() + } +} diff --git a/crates/ruff_python_semantic/src/lib.rs b/crates/ruff_python_semantic/src/lib.rs index 30128e23a5..38d6da4701 100644 --- a/crates/ruff_python_semantic/src/lib.rs +++ b/crates/ruff_python_semantic/src/lib.rs @@ -1,6 +1,7 @@ pub mod analyze; mod binding; mod branches; +pub mod cfg; mod context; mod definition; mod globals; diff --git a/pyproject.toml b/pyproject.toml index 42738254ef..fd06e4ce12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,8 @@ extend-exclude = [ "crates/ruff/resources/", "crates/ruff_linter/resources/", "crates/ruff_python_formatter/resources/", - "crates/ruff_python_parser/resources/" + "crates/ruff_python_parser/resources/", + "crates/ruff_python_semantic/resources/" ] [tool.ruff.lint]