Skip to content

Commit 0d2a7fc

Browse files
Merge pull request RustPython#1321 from RustPython/try-return-finally
Refactor block stack unwinding to enable finally execution.
2 parents 47ddc7c + 2ca6e5d commit 0d2a7fc

File tree

4 files changed

+242
-145
lines changed

4 files changed

+242
-145
lines changed

bytecode/src/bytecode.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,26 @@ pub enum Instruction {
178178
start: Label,
179179
end: Label,
180180
},
181+
182+
/// Setup a finally handler, which will be called whenever one of this events occurs:
183+
/// - the block is popped
184+
/// - the function returns
185+
/// - an exception is returned
186+
SetupFinally {
187+
handler: Label,
188+
},
189+
190+
/// Enter a finally block, without returning, excepting, just because we are there.
191+
EnterFinally,
192+
193+
/// Marker bytecode for the end of a finally sequence.
194+
/// When this bytecode is executed, the eval loop does one of those things:
195+
/// - Continue at a certain bytecode position
196+
/// - Propagate the exception
197+
/// - Return from a function
198+
/// - Do nothing at all, just continue
199+
EndFinally,
200+
181201
SetupExcept {
182202
handler: Label,
183203
},
@@ -475,7 +495,10 @@ impl Instruction {
475495
YieldValue => w!(YieldValue),
476496
YieldFrom => w!(YieldFrom),
477497
SetupLoop { start, end } => w!(SetupLoop, label_map[start], label_map[end]),
478-
SetupExcept { handler } => w!(SetupExcept, handler),
498+
SetupExcept { handler } => w!(SetupExcept, label_map[handler]),
499+
SetupFinally { handler } => w!(SetupFinally, label_map[handler]),
500+
EnterFinally => w!(EnterFinally),
501+
EndFinally => w!(EndFinally),
479502
SetupWith { end } => w!(SetupWith, end),
480503
CleanupWith { end } => w!(CleanupWith, end),
481504
PopBlock => w!(PopBlock),

compiler/src/compile.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,20 @@ impl<O: OutputStream> Compiler<O> {
708708
&mut self,
709709
body: &[ast::Statement],
710710
handlers: &[ast::ExceptHandler],
711-
orelse: &Option<Vec<ast::Statement>>,
712-
finalbody: &Option<Vec<ast::Statement>>,
711+
orelse: &Option<ast::Suite>,
712+
finalbody: &Option<ast::Suite>,
713713
) -> Result<(), CompileError> {
714714
let mut handler_label = self.new_label();
715-
let finally_label = self.new_label();
715+
let finally_handler_label = self.new_label();
716716
let else_label = self.new_label();
717+
718+
// Setup a finally block if we have a finally statement.
719+
if finalbody.is_some() {
720+
self.emit(Instruction::SetupFinally {
721+
handler: finally_handler_label,
722+
});
723+
}
724+
717725
// try:
718726
self.emit(Instruction::SetupExcept {
719727
handler: handler_label,
@@ -765,26 +773,28 @@ impl<O: OutputStream> Compiler<O> {
765773
// Handler code:
766774
self.compile_statements(&handler.body)?;
767775
self.emit(Instruction::PopException);
776+
777+
if finalbody.is_some() {
778+
self.emit(Instruction::PopBlock); // pop finally block
779+
// We enter the finally block, without exception.
780+
self.emit(Instruction::EnterFinally);
781+
}
782+
768783
self.emit(Instruction::Jump {
769-
target: finally_label,
784+
target: finally_handler_label,
770785
});
771786

772787
// Emit a new label for the next handler
773788
self.set_label(handler_label);
774789
handler_label = self.new_label();
775790
}
791+
776792
self.emit(Instruction::Jump {
777793
target: handler_label,
778794
});
779795
self.set_label(handler_label);
780796
// If code flows here, we have an unhandled exception,
781-
// emit finally code and raise again!
782-
// Duplicate finally code here:
783-
// TODO: this bytecode is now duplicate, could this be
784-
// improved?
785-
if let Some(statements) = finalbody {
786-
self.compile_statements(statements)?;
787-
}
797+
// raise the exception again!
788798
self.emit(Instruction::Raise { argc: 0 });
789799

790800
// We successfully ran the try block:
@@ -794,12 +804,20 @@ impl<O: OutputStream> Compiler<O> {
794804
self.compile_statements(statements)?;
795805
}
796806

807+
if finalbody.is_some() {
808+
self.emit(Instruction::PopBlock); // pop finally block
809+
810+
// We enter the finally block, without return / exception.
811+
self.emit(Instruction::EnterFinally);
812+
}
813+
797814
// finally:
798-
self.set_label(finally_label);
815+
self.set_label(finally_handler_label);
799816
if let Some(statements) = finalbody {
800817
self.compile_statements(statements)?;
818+
self.emit(Instruction::EndFinally);
801819
}
802-
// unimplemented!();
820+
803821
Ok(())
804822
}
805823

tests/snippets/try_exceptions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,19 @@ def f():
187187
pass
188188
raise
189189

190+
# try-return-finally behavior:
191+
l = []
192+
def foo():
193+
try:
194+
return 33
195+
finally:
196+
l.append(1337)
197+
198+
r = foo()
199+
assert r == 33
200+
assert l == [1337]
201+
202+
190203
# Regression https://github.com/RustPython/RustPython/issues/867
191204
for _ in [1, 2]:
192205
try:

0 commit comments

Comments
 (0)