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]