Understanding Ruby's block/proc parsing

penelope_zone profile image Penelope Phippen ・2 min read

In Ruby, methods can take a single block as an argument, which can be specified explicitly in the method signature by writing:

def some_method(&blk)

or implicitly in the method body by writing:

def some_method
  yield if block_given?

There are a few ways of specifying the block to the method:

def some_method(&blk)
  p blk


some_method do

some_method { foo }

These are all (roughly) equivalent, calling the foo method in the place of the passed blk parameter. How they are parsed, however, is very different:

Passing an expression with &

Imagine we call this method as: some_method(&:foo). The parse tree looks like this:

   [:fcall, [:@ident, "some_method", [1, 0]]],
     [:symbol_literal, [:symbol, [:@ident, "foo", [1, 14]]]]]]]]]

Specifically, the argument structure to the call is parsed as a parser node called args_add_block, which contains an expression list (that's the empty array in the code example above), and a single expression at the end (that's the :symbol_literal expression that represents the to_proc expression). The first expression list is the non proc arguments to the call.

Passing a block

Imagine we call the method as: some_method { foo }. The parse tree looks like this:

   [:method_add_arg, [:fcall, [:@ident, "some_method", [1, 0]]], []],
   [:brace_block, nil, [[:vcall, [:@ident, "foo", [1, 14]]]]]]]]

in particular, note that we no longer have an args_add_block parse node. This is because in this style of call, the block is not treated as part of the arguments to the call by the parser, instead, it is treated as a modifier to the method call, that changes which call type is being made.

why did you write this?

This acts as documentation for the ongoing development of Rubyfmt.

Posted on by:

penelope_zone profile

Penelope Phippen


Once described as "a very charming nightmare."


markdown guide

Great to also see you on here, Penelope :-) The link to Rubyfmt at the end seems wrong, as it uses your Twitter instead of your GH username. Until the post is fixed, here's the correct link for anyone curious:



Can you share how you are generating the parse trees in these examples? 🤔 It seems like it would be fun to play around with!


It's a library in Ruby called ripper:

require "ripper"
pp Ripper.sexp("hi")
# => [:program, [[:vcall, [:@ident, "hi", [1,0]]]]]