mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 21:25:08 +00:00
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:
parent
755ece0c36
commit
d401a5440e
7 changed files with 194 additions and 14 deletions
18
crates/ruff_python_semantic/resources/test/fixtures/cfg/jumps.py
vendored
Normal file
18
crates/ruff_python_semantic/resources/test/fixtures/cfg/jumps.py
vendored
Normal 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")
|
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue