crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.2k stars 1.61k forks source link

Captured block parameter not recognised when used inside macro #14727

Open syalon opened 1 week ago

syalon commented 1 week ago

my test code

def assert(cond, &err : -> String)
  {% if flag?(:debug) %}
    raise err.call unless cond
  {% end %}
end

assert(false) { "test error" }

compile result

Showing last frame. Use --error-trace for full trace.

There was a problem expanding macro 'macro_140633862877744'

Called macro defined in eval:2:3

Which expanded to:

 > 1 | 
 > 2 |     raise err.call unless cond
                 ^--
Error: undefined local variable or method 'err' for top-level

try online: https://play.crystal-lang.org/#/r/gxy9

Blacksmoke16 commented 1 week ago

You probably want to use a macro here:

macro assert(cond, &)
  {% if flag?(:debug) %}
    raise {{yield}} unless {{cond}}
  {% end %}
end

Related: https://github.com/crystal-lang/crystal/issues/4263

Otherwise this is essentially a duplicate of https://github.com/crystal-lang/crystal/issues/7975

straight-shoota commented 1 week ago

This is not exacty #7975. The variable is not declared within the macro. It's only used there.

But it seems to be a similar issue. The compiler determines whether a block argument is a caputered block depending on whether the variable is referenced in the method body. Apparently it cannot recognise it when inside a macro. So this relates to #8764.

It's easy to work around by inserting an explicit reference to the block variable outside the macro.

def assert(cond, &err : -> String)
  err
  {% if flag?(:debug) %}
    raise err.call unless cond
  {% end %}
end