Project

General

Profile

Feature #12906

do/end blocks work with ensure/rescue/else

Added by josh.cheek (Josh Cheek) about 1 year ago. Updated 10 months ago.

Status:
Closed
Priority:
Normal
Target version:
[ruby-core:78021]

Description

When you want to rescue in a block, you must do this:

lambda do
  begin
    raise 'err'
  rescue
    $! # => #<RuntimeError: err>
  end
end.call

I've wished on numerous occasions that I could omit the begin/end and not need the extra wrapper:

lambda do
  raise 'err'
rescue
  $! # => #<RuntimeError: err>
end.call

This would be consistent with how classes and methods work:

class C
  raise 'err'
rescue
  $! # => #<RuntimeError: err>
end

send def m
  raise 'err'
rescue
  $! # => #<RuntimeError: err>
end

It's not really clear to me how to submit this since it may require some discussion, but this is the diff:

diff --git a/parse.y b/parse.y
index 54ccc52..223e5d3 100644
--- a/parse.y
+++ b/parse.y
@@ -3757,7 +3757,7 @@ brace_body    : {$<vars>$ = dyna_push();}

 do_body    : {$<vars>$ = dyna_push();}
          {$<val>$ = cmdarg_stack >> 1; CMDARG_SET(0);}
-         opt_block_param compstmt
+         opt_block_param bodystmt
            {
            $$ = new_do_body($3, $4);
            dyna_pop($<vars>1);

I added tests for ensure to rubyspec, but there wasn't an obvious place to talk about rescue/else in this context (the spec for rescue only uses it in a begin/end block) It's probably fine as the spec for ensure does hit rescue, too, and they ultimately delegate to the same pieces. Not totally clear, though. I can do more with that if you need.

diff --git a/language/ensure_spec.rb b/language/ensure_spec.rb
index 13575fc..b14b0b5 100644
--- a/language/ensure_spec.rb
+++ b/language/ensure_spec.rb
@@ -124,3 +124,74 @@ describe "An ensure block inside a method" do
     @obj.explicit_return_in_method_with_ensure.should == :ensure
   end
 end
+
+describe "An ensure block inside a do block" do
+  before :each do
+    ScratchPad.record []
+  end
+
+  it "is executed when an exception is raised in it's corresponding do block" do
+    begin
+      lambda do
+        ScratchPad << :begin
+        raise "An exception occured!"
+      ensure
+        ScratchPad << :ensure
+      end.should raise_error(RuntimeError)
+
+      ScratchPad.recorded.should == [:begin, :ensure]
+    end
+  end
+
+  it "is executed when an exception is raised and rescued in it's corresponding do block" do
+    begin
+      lambda do
+        ScratchPad << :begin
+        raise "An exception occured!"
+      rescue
+        ScratchPad << :rescue
+      ensure
+        ScratchPad << :ensure
+      end.call
+
+      ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+    end
+  end
+
+  it "is executed even when a symbol is thrown in it's corresponding do block" do
+    begin
+      catch(:symbol) do
+        lambda do
+          ScratchPad << :begin
+          throw(:symbol)
+        rescue
+          ScratchPad << :rescue
+        ensure
+          ScratchPad << :ensure
+        end.call
+      end
+
+      ScratchPad.recorded.should == [:begin, :ensure]
+    end
+  end
+
+  it "is executed when nothing is raised or thrown in it's corresponding do block" do
+    lambda do
+      ScratchPad << :begin
+    rescue
+      ScratchPad << :rescue
+    ensure
+      ScratchPad << :ensure
+    end.call
+
+    ScratchPad.recorded.should == [:begin, :ensure]
+  end
+
+  it "has no return value" do
+    lambda do
+      :begin
+    ensure
+      :ensure
+    end.call.should == :begin
+  end
+end

Related issues

Is duplicate of Ruby trunk - Feature #7882: Allow rescue/else/ensure in do..endClosed
Is duplicate of Ruby trunk - Feature #11337: Allow rescue without begin inside blocksClosed
Is duplicate of CommonRuby - Feature #12623: rescue in blocks without begin/endClosed
Is duplicate of Ruby trunk - Feature #13212: Syntax proposal: don't require begin-end to rescue exceptions inside do-end blocksClosed

Associated revisions

Revision 57376
Added by nobu (Nobuyoshi Nakada) 10 months ago

parse.y: rescue/else/ensure in do-end

  • parse.y (do_body): allow rescue/else/ensure inside do/end blocks. [Feature #12906]

History

#1 Updated by shyouhei (Shyouhei Urabe) about 1 year ago

  • Is duplicate of Feature #7882: Allow rescue/else/ensure in do..end added

#2 Updated by shyouhei (Shyouhei Urabe) about 1 year ago

  • Is duplicate of Feature #11337: Allow rescue without begin inside blocks added

#3 Updated by shyouhei (Shyouhei Urabe) about 1 year ago

  • Is duplicate of Feature #12623: rescue in blocks without begin/end added

#4 [ruby-core:78025] Updated by shyouhei (Shyouhei Urabe) about 1 year ago

  • Assignee set to matz (Yukihiro Matsumoto)
  • Status changed from Open to Assigned

Josh Cheek wrote:

but this is the diff:

diff --git a/parse.y b/parse.y
index 54ccc52..223e5d3 100644
--- a/parse.y
+++ b/parse.y
@@ -3757,7 +3757,7 @@ brace_body  : {$<vars>$ = dyna_push();}

 do_body  : {$<vars>$ = dyna_push();}
        {$<val>$ = cmdarg_stack >> 1; CMDARG_SET(0);}
-       opt_block_param compstmt
+       opt_block_param bodystmt
          {
          $$ = new_do_body($3, $4);
          dyna_pop($<vars>1);

So from the patch you sent, I guess you are implicitly proposing to forget about {...}-style blocks for a while and focus on do ... end-style blocks. That is in fact a wise idea. To start small is a wisdom we learned in this forum.

#5 [ruby-core:78027] Updated by josh.cheek (Josh Cheek) about 1 year ago

My error on the duplication, I tried searching a couple different ways but the first results weren't about this and I didn't see how to use the filters and the keywords together. Fortunately it seems congruent with https://bugs.ruby-lang.org/issues/7882

Shyouhei Urabe wrote:

So from the patch you sent, I guess you are implicitly proposing to forget about {...}-style blocks for a while and focus on do ... end-style blocks. That is in fact a wise idea. To start small is a wisdom we learned in this forum.

Ty :) the existing places it works are begin/end, def/end, class/end, so it felt unifying to add do/end, but seemed less natural with curly braces. I'm sure I'd get used to it, if it were there, though.

#6 [ruby-core:78052] Updated by shevegen (Robert A. Heiler) about 1 year ago

I like the idea. It would get rid of one begin/end clause if I understood
it correctly and be on the same level as class C/rescue/end definitions.

#7 [ruby-core:78446] Updated by josh.cheek (Josh Cheek) 12 months ago

Checking that I submitted this correctly, I usually do Github, so might have gotten this wrong.

#8 [ruby-core:79153] Updated by matz (Yukihiro Matsumoto) 10 months ago

  • Target version set to 2.5
  • Assignee changed from matz (Yukihiro Matsumoto) to nobu (Nobuyoshi Nakada)

Although I am not a big fan of this syntax, mostly because I don't like fine grain exception handling.
But I found out many developers prefer the syntax. After some consideration, I decided to accept this.

Matz.

#9 Updated by nobu (Nobuyoshi Nakada) 10 months ago

  • Status changed from Assigned to Closed

Applied in changeset r57376.


parse.y: rescue/else/ensure in do-end

  • parse.y (do_body): allow rescue/else/ensure inside do/end blocks. [Feature #12906]

#10 Updated by hsbt (Hiroshi SHIBATA) 9 months ago

  • Is duplicate of Feature #13212: Syntax proposal: don't require begin-end to rescue exceptions inside do-end blocks added

Also available in: Atom PDF