Skip to content

Control flow graph: setup #17064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/ruff_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ license = { workspace = true }
ruff_annotate_snippets = { workspace = true }
ruff_cache = { workspace = true }
ruff_diagnostics = { workspace = true, features = ["serde"] }
ruff_index = { workspace = true }
ruff_notebook = { workspace = true }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true, features = ["serde", "cache"] }
Expand Down
275 changes: 13 additions & 262 deletions crates/ruff_linter/resources/test/fixtures/pylint/unreachable.py
Original file line number Diff line number Diff line change
@@ -1,263 +1,14 @@
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)
def empty_statement_reachable(): ...

def pass_statement_reachable():
pass

def no_control_flow_reachable():
x = 1
x = 2
class C:
a = 2
c = C()
del c
def foo():
return
else:
frame = DataFrame(
mat, columns=["A", "B", "C"], index=[1, 2], dtype=np.int64
)
assert frame.values.dtype == np.int64

# wrong size axis labels
msg = r"Shape of passed values is \(2, 3\), indices imply \(1, 3\)"
with pytest.raises(ValueError, match=msg):
DataFrame(mat, columns=["A", "B", "C"], index=[1])
msg = r"Shape of passed values is \(2, 3\), indices imply \(2, 2\)"
with pytest.raises(ValueError, match=msg):
DataFrame(mat, columns=["A", "B"], index=[1, 2])

# higher dim raise exception
with pytest.raises(ValueError, match="Must pass 2-d input"):
DataFrame(empty((3, 3, 3)), columns=["A", "B", "C"], index=[1])

# automatic labeling
frame = DataFrame(mat)
tm.assert_index_equal(frame.index, Index(range(2)), exact=True)
tm.assert_index_equal(frame.columns, Index(range(3)), exact=True)

frame = DataFrame(mat, index=[1, 2])
tm.assert_index_equal(frame.columns, Index(range(3)), exact=True)

frame = DataFrame(mat, columns=["A", "B", "C"])
tm.assert_index_equal(frame.index, Index(range(2)), exact=True)

# 0-length axis
frame = DataFrame(empty((0, 3)))
assert len(frame.index) == 0

frame = DataFrame(empty((3, 0)))
assert len(frame.columns) == 0


def after_return():
return "reachable"
print("unreachable")
print("unreachable")
print("unreachable")
print("unreachable")
print("unreachable")


def check_if_url_exists(url: str) -> bool: # type: ignore[return]
return True # uncomment to check URLs
response = requests.head(url, allow_redirects=True)
if response.status_code == 200:
return True
if response.status_code == 404:
return False
console.print(f"[red]Unexpected error received: {response.status_code}[/]")
response.raise_for_status()
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/checkers/ast/analyze/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
#[cfg(any(feature = "test-rules", test))]
if checker.enabled(Rule::UnreachableCode) {
checker.report_diagnostics(pylint::rules::in_function(name, body));
pylint::rules::in_function(checker, name, body);
}
if checker.enabled(Rule::ReimplementedOperator) {
refurb::rules::reimplemented_operator(checker, &function_def.into());
Expand Down
Loading
Loading