Crystal: Error with macro expansion when spawning shell commands

Created on 22 Mar 2019  路  4Comments  路  Source: crystal-lang/crystal

Running:

spawn `echo FOO`

leads to macro expansion error:

Error in line 1: macro didn't expand to a valid program, it expanded to:

================================================================================
--------------------------------------------------------------------------------
   1.   
   2.     ->(
   3.       
   4.         __arg0 : typeof("echo FOO"),
   5.       
   6.       
   7.       ) {
   8.       spawn(name: nil) do
   9.         `(
  10.           
  11.             __arg0,
  12.           
  13.           
  14.         )
  15.       end
  16.     
  17.       }.call("echo FOO")
  18.     
  19.   
--------------------------------------------------------------------------------
Syntax error in expanded macro: spawn:20: Unterminated command literal
================================================================================

https://play.crystal-lang.org/#/r/6jp4

bug lang topicconcurrency topicmacros

Most helpful comment

Another alternative is to completely remove the backtick thing. I think they wanted to remove it from Ruby 3 too (but can't because it's a massive breaking change). That means removing $? too, which, to be honest, isn't very useful ($~, $1, etc., are a bit more useful because they can result from when /regex/).

All 4 comments

That's spawn { `echo FOO` }

The issue here is that the spawn macro when given a call argument, takes it apart and basically delegates the arguments from outside the block to avoid creating a closure. The command literal `echo FOO` is internally represented as a call to the top-level method .`. So it is essentially `("echo FOO"), but that is not a valid call in Crystal syntax, because the backtick is interpreted as a delimiter for the command literal. There is no way to differentiate whether its a delimiter or method name.

Usually, this is not a problem because calls to .` are simply stringified as the equivalent literal.

But spawn doesn't take this into account. We could simply fix this by adding an exception to spawn if call.name == ""`.

I'm not too comfortable with this solution though, because it propagates an exception which always needs mitigation in such cases. It would be better if the call equivalent of the command literal would simply be a valid call with no special handling. I suppose we could just change the method name to double backticks. There would still be some ambiguity because `` could also be an empty literal. But when followed by an argument (with or w/o parenthesis) it's a call. This shouldn't be a very complicated rule.

The obvious workaround is spawn { `echo FOO` }, but spawn `echo FOO` should work as well (although I'm not sure there is a valid use case for this particular example).

Another alternative is to completely remove the backtick thing. I think they wanted to remove it from Ruby 3 too (but can't because it's a massive breaking change). That means removing $? too, which, to be honest, isn't very useful ($~, $1, etc., are a bit more useful because they can result from when /regex/).

Replacing the literal with a regular string + method call could also help for #7171

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cjgajard picture cjgajard  路  3Comments

nabeelomer picture nabeelomer  路  3Comments

jhass picture jhass  路  3Comments

costajob picture costajob  路  3Comments

oprypin picture oprypin  路  3Comments