Crystal: Block bodies in macros should always be Expressions

Created on 8 Nov 2017  路  5Comments  路  Source: crystal-lang/crystal

Currently in macros, bodies of blocks of code can either be Expressions, Nop, or any other type of ASTNode - depending on how many expressions/ statements there are. This is the case for control flow, blocks, methods, etc. Iterating over the expressions is difficult as you have to check what type the body is before using it.

macro print_methods(&block)
  {% for method in block.body.expressions %}
    {% puts method.name %}
  {% end %}
end

print_methods do
  def foo
  end
  def bar
  end
end
# Prints:
# foo
# bar

print_methods do
  def foo
  end
end
# Undefined macro method 'Def#expressions'

This can be worked around, but the workaround gets tedious:

macro print_methods(&block)
  {% expressions = block.body.is_a?(Expressions) ? block.body.expressions : [block.body] %}
  {% for method in expressions %}
    {% puts method.name %}
  {% end %}
end

I think that the simplest / cleanest solution would be for the body to be an Expressions object, containing some number of nodes (zero or more). There could be a helper method to check if it is a no-op (ie it contains no elements in the list) which could replace the use of .is_a?(Nop).

Crystal version (pretty close to master)

$ crystal --version
Crystal 0.24.0+3 [9b1a9e7] (2017-11-01) 
LLVM: 3.8.0
Default target: x86_64-pc-linux-gnu
$ uname -a
Linux ed 4.4.0-92-generic #115-Ubuntu SMP Thu Aug 10 09:04:33 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Most helpful comment

@asterite Absolutely not, it's counter-intuitive to me that that does work. And certainly less powerful.

"A block body is an Expressions unless it's only one expression" is a lot more complex and introduces a lot more complexity (when you don't have the power of visitor classes) then "A block body is always an Expressions".

All 5 comments

If you want to pass one or many arguments to a macro, use a splat:

print_methods(
  def foo
  end,
  def bar
  end
)

I don't think we'll change macros to do what you want, it's counter-intuitive and expensive, and it changes what you send and what you print.

Why is it counter-intuitive for a block body to ever be an expressions of size 1?

macro foo(&block)
  {{ block.body.is_a?(If) }}
end

foo do
  if 1; 2; end
end

Wouldn't it be counter-intuitive that the above gives false?

In you example @asterite I think it is counter-intuitive to have:

foo do
  if 1; 2; end
end
# Gives true


foo do
  if 1; 2; end
  if 3; 4; end
end
# Gives false

@asterite Absolutely not, it's counter-intuitive to me that that does work. And certainly less powerful.

"A block body is an Expressions unless it's only one expression" is a lot more complex and introduces a lot more complexity (when you don't have the power of visitor classes) then "A block body is always an Expressions".

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pbrusco picture pbrusco  路  3Comments

asterite picture asterite  路  3Comments

oprypin picture oprypin  路  3Comments

RX14 picture RX14  路  3Comments

Sija picture Sija  路  3Comments