Project

General

Profile

Feature #12906

do/end blocks work with ensure/rescue/else

Added by josh.cheek (Josh Cheek) over 1 year ago. Updated about 2 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 0ec889d7
Added by nobu (Nobuyoshi Nakada) over 1 year ago

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

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

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@57376 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

Revision 57376
Added by nobu (Nobuyoshi Nakada) over 1 year ago

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

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

Revision 57376
Added by nobu (Nobuyoshi Nakada) over 1 year 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) over 1 year ago

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

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

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

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

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

#4 [ruby-core:78025] Updated by shyouhei (Shyouhei Urabe) over 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) over 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) over 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) over 1 year 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) over 1 year 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) over 1 year 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) over 1 year ago

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

#11 [ruby-core:87273] Updated by perlun (Per Lundberg) about 2 months ago

Old issue, but still perhaps the right place to mention this: the new syntax ONLY works in do/end, not in {} blocks (as mentioned above.)

It also does not work in "block-like" places like a for loop. So this is not valid syntax:

for i in 1..100
  puts 'hello'
rescue # Syntax error; not valid code in existing Ruby versions like 2.5.0 and 2.5.1.
end

Suggested workaround: Use a (1..100).each do |i| construct instead. Then you can use a rescue or ensure within the block in Ruby 2.5 and newer.

(This is actually an arguably valid use case for "fine grained error handling", in cases where the loop can fail in n of the 100 cases and you want the failing ones to be ignored. I agree that too-finegrained error handling can otherwise become an antipattern.)

Also available in: Atom PDF