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