Control flow: return and raise (#17121)

We add support for `return` and `raise` statements in the control flow
graph: we simply add an edge to the terminal block, push the statements
to the current block, and proceed.

This implementation will have to be modified somewhat once we add
support for `try` statements - then we will need to check whether to
_defer_ the jump. But for now this will do!

Also in this PR: We fix the `unreachable` diagnostic range so that it
lumps together consecutive unreachable blocks.
This commit is contained in:
Dylan 2025-04-03 08:30:29 -05:00 committed by GitHub
parent 755ece0c36
commit d401a5440e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 194 additions and 14 deletions

View file

@ -0,0 +1,18 @@
def only_return():
return
def after_return():
return 1
print("unreachable")
print("and this")
def after_raise():
raise ValueError
print("unreachable")
print("and this")
def multiple_returns():
return 1
print("unreachable")
return 2
print("and this")

View file

@ -192,8 +192,12 @@ impl<'stmt> CFGBuilder<'stmt> {
/// Runs the core logic for the builder.
fn process_stmts(&mut self, stmts: &'stmt [Stmt]) {
let start = 0;
for stmt in stmts {
// SAFETY With notation as below, we always maintain the invariant
// `start <= end + 1`. Since `end <= stmts.len() -1` we conclude that
// `start <= stmts.len()`. It is therefore always safe to use `start` as
// the beginning of a range for the purposes of slicing into `stmts`.
let mut start = 0;
for (end, stmt) in stmts.iter().enumerate() {
let cache_exit = self.exit();
match stmt {
Stmt::FunctionDef(_)
@ -223,10 +227,30 @@ impl<'stmt> CFGBuilder<'stmt> {
Stmt::With(_) => {}
// Jumps
Stmt::Return(_) => {}
Stmt::Return(_) => {
let edges = Edges::always(self.cfg.terminal());
self.set_current_block_stmts(&stmts[start..=end]);
self.set_current_block_edges(edges);
start = end + 1;
if stmts.get(start).is_some() {
let next_block = self.new_block();
self.move_to(next_block);
}
}
Stmt::Break(_) => {}
Stmt::Continue(_) => {}
Stmt::Raise(_) => {}
Stmt::Raise(_) => {
let edges = Edges::always(self.cfg.terminal());
self.set_current_block_stmts(&stmts[start..=end]);
self.set_current_block_edges(edges);
start = end + 1;
if stmts.get(start).is_some() {
let next_block = self.new_block();
self.move_to(next_block);
}
}
// An `assert` is a mixture of a switch and a jump.
Stmt::Assert(_) => {}
@ -269,6 +293,11 @@ impl<'stmt> CFGBuilder<'stmt> {
self.current = block;
}
/// Makes new block and returns index
fn new_block(&mut self) -> BlockId {
self.cfg.blocks.push(BlockData::default())
}
/// Populates the current basic block with the given set of statements.
///
/// This should only be called once on any given block.

View file

@ -16,6 +16,7 @@ mod tests {
use test_case::test_case;
#[test_case("no_flow.py")]
#[test_case("jumps.py")]
fn control_flow_graph(filename: &str) {
let path = PathBuf::from("resources/test/fixtures/cfg").join(filename);
let source = fs::read_to_string(path).expect("failed to read file");

View file

@ -0,0 +1,81 @@
---
source: crates/ruff_python_semantic/src/cfg/mod.rs
description: "This is a Mermaid graph. You can use https://mermaid.live to visualize it as a diagram."
---
## Function 0
### Source
```python
def only_return():
return
```
### Control Flow Graph
```mermaid
flowchart TD
node0["return"]
node1((("EXIT")))
node0==>node1
```
## Function 1
### Source
```python
def after_return():
return 1
print("unreachable")
print("and this")
```
### Control Flow Graph
```mermaid
flowchart TD
node0["return 1"]
node1((("EXIT")))
node2["print(#quot;unreachable#quot;)
print(#quot;and this#quot;)"]
node0==>node1
node2==>node1
```
## Function 2
### Source
```python
def after_raise():
raise ValueError
print("unreachable")
print("and this")
```
### Control Flow Graph
```mermaid
flowchart TD
node0["raise ValueError"]
node1((("EXIT")))
node2["print(#quot;unreachable#quot;)
print(#quot;and this#quot;)"]
node0==>node1
node2==>node1
```
## Function 3
### Source
```python
def multiple_returns():
return 1
print("unreachable")
return 2
print("and this")
```
### Control Flow Graph
```mermaid
flowchart TD
node0["return 1"]
node1((("EXIT")))
node2["print(#quot;unreachable#quot;)
return 2"]
node3["print(#quot;and this#quot;)"]
node0==>node1
node2==>node1
node3==>node1
```