The difference between using do ... end and { ... } is that do ... end binds to the left-most call, while { ... } binds to the right-most call
However, this doesn’t match the actual behavior I see:
def a(*args) end
def a(*args)
puts "a received block"
yield
end
def b(*args) end
def b(*args)
puts "b received block"
yield
end
def c(*args) end
def c(*args)
puts "c received block"
yield
end
a b c do
puts "in block"
end
Rather than binding to the left-most call, it seems do/end blocks bind to the second-from-the-right call. Is this behavior intended?
For comparison, here is an equivalent Ruby snippet:
def a(*args)
if block_given?
puts "a received block"
yield
end
end
def b(*args)
if block_given?
puts "b received block"
yield
end
end
def c(*args)
if block_given?
puts "c received block"
yield
end
end
a b c do
puts "in block"
end
def a(*args)
puts "a"
end
def a(*args, &)
puts "a received block"
yield
end
def b(*args)
puts "b"
end
def b(*args, &)
puts "b received block"
yield
end
def c(*args)
puts "c"
end
def c(*args, &)
puts "c received block"
yield
end
a b c do
puts "in block"
end
c
b received block
in block
a
If this code works on ruby, do ... end block should bind to the a, but if replace with {...}, it bind to c.
I’d report it on the github issue tracker, as at the very least it is a bug in the documentation. Though the behavior seems weird so it is probably not intended.
I always felt this difference in behaviour is quite extraordinary. And probably more confusing than helpful.
It’s a good recommendation to always use parenthesis when there is doubt. Maybe the formatter should do that?
Anyway while looking briefly at the parser code, it seems obvious why this is failing: To work correctly as described, this feature would require backtracking through a chain of calls in order to attach do to the leftmost one. But the parser doesn’t do that. It’s just a simple boolean flag that indicates to stop parsing the next call on a do keyword. Then the previous call continues.
So tentatively a fix could turn that boolean into a counter to keep track of multiple nested calls.
Btw. in the source code I noticed another quirk: parentheses breaks the left binding: