Advent of Prism: Part 15 - Call arguments
This blog series is about how the prism Ruby parser works. If you’re new to the series, I recommend starting from the beginning. This post is about call arguments.
Today we’re going to talk about the nodes we use to represent the arguments to a method call. Let’s get into it.
The general list of methods that we pass to a method call is represented by an
ArgumentsNode. This node is effectively a wrapper around the list of arguments. It is present when there is one or more arguments, and
nil if there are no arguments. Here’s an example:
foo(1, 2, 3)
This is represented by the following AST:
You’ll notice there’s an explicit field for flags. This is only used for a single flag, which indicates the presence of a splat operator within the list of arguments. If there is a splat operator, then compilers cannot statically determine the number of arguments, and must instead determine it at runtime. So if compilers want to take a different path through the code depending on whether or not there is a splat operator, they can check this flag.
Argument lists also appear on a couple of keywords:
return. We’ve covered
super already, but will get to the others soon.
When you pass a block to a method with the
& operator, we create a
BlockArgumentNode. This syntax implies that
#to_proc should be called on the argument and passed to the method. Here’s an example:
The above snippet is represented by the following AST:
The expression is actually optional if you’re within a method definition that has an anonymous block. For example:
This syntax means to forward the block from the
foo method call down to the
bar method call. This is also represented by a
BlockArgumentNode, but with a
nil expression. For example, the
bar(&) in the above example:
You can use the
... operator to forward all arguments types (positional, keyword, and block) to a method call. This is represented by a
ForwardingArgumentsNode. Here’s an example:
This will be represented by the following AST:
In terms of actually parsing this, it’s relatively simple. The
... operator can only appear in an argument list if it has been declared in the current method’s parameter list. Internally we take a shortcut by adding
... to the local table and then checking it as we would any other identifier.
When you use internal hash syntax within an argument list, we create a
KeywordHashNode. Here are a couple of examples:
foo(:bar => 1)
In all of these cases we create a
KeywordHashNode. The last two lines will be passed as a hash argument. The first line it depends on how the method was declared; it could end up being a hash in the first position or a keyword. Here’s what the AST looks like for the first example:
When you use the
* unary operator, we create a
SplatNode. This can appear in a couple of different places. It can either imply to spread out a list of values or to group them. Here are a couple of examples:
foo, * = bar
foo, = *baz
foo in [*bar]
Today we’ll only be looking at the first example. The others have either already been covered or will be covered in their own posts. Here’s what the AST looks like for
Note that the
expression field is optional. Just like the
& operator, it can be used to forward arguments, as in:
The AST for the method call in this example looks like this:
The last type of node we’ll look at today is
ImplicitNode. This node is used to represent an implicit hash key. While not exclusively used in method calls (it can also be used in plain hashes) it is most commonly used in keyword arguments. Here is an example:
This is represented by the following AST:
Note that an implicit node effectively wraps the node that would have been present if it were explicit. We wrap it so that we don’t have to have an
ImplicitConstantReadNode. Instead it’s a marker that the following subtree is implicit.
It can also be used to represent local variables, as in:
bar = 1
Finally, it can be used to look up constants, as in:
That results in the following AST:
Today we looked at the many different kinds of arguments to method calls. Here are a couple of things to remember from today’s post:
ArgumentsNodewill always be present in the event of one or more arguments.
SplatNodedo not necessarily have an attached expression.
ImplicitNodeis used to represent implicit hash values based on their key.
Tomorrow we’ll wrap up our discussion of method calls by looking at the most complicated form: control-flow calls.← Back to home