crystal-lang / crystal

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

Short block syntax with macro yield #11012

Open straight-shoota opened 3 years ago

straight-shoota commented 3 years ago

Using short block syntax with macro yield is broken because the implicit receiver is mapped to a non-existent local variable instead of the macro block argument.

macro foo
  {{ yield "foo" }}
end

foo &.upcase # Error: undefined local variable or method '__arg0' for top-level

# For reference, this works:
foo {|x| {{x}}.upcase } # => "FOO"

It's possible to trick the compiler by sneaking in the block var like this:

macro foo
  {{ %(__arg0 = "foo").id }}
  {{ yield }}
end

foo(&.upcase) # => "FOO"

This workaround is not very user-friendly and has limitations when the argument is not a literal but a fresh variable for example.

HertzDevil commented 3 years ago

foo { |x| x.upcase } doesn't work either, so it would be weird if only the short blocks expand to { |x| {{ x }}.upcase }.

straight-shoota commented 3 years ago

Why would it be weird? The short block syntax is supposed to construct a call on the first block argument. That's what it should do, even in a macro call.

The current behaviour for macro calls is insufficient because the injected local variable (e.g. __arg0) does not exist. The generated code is broken. We should fix that. Alternatively, we could remove short block syntax from macro calls entirely because it's unusable.

HertzDevil commented 3 years ago

Because then &.upcase would have a different expansion depending on whether foo resolves to a macro call, and the parser definitely doesn't have enough information for that.

straight-shoota commented 3 years ago

Sure, this needs to be handled at the semantic stage when we know whether the call is for a macro or a method.

It's just a technical question of how to implement it. I don't think it should be hard to do really. Block could get a flag to designate it as being expanded short block syntax. The body is always a single Call. The macro interpreter can pick up on the flag and replace the receiver of the call.

asterite commented 3 years ago

I don't think yield with an argument is implemented well in the language. So maybe the best thing to do is to leave this feature alone, and eventually deprecate it. It's confusing.

straight-shoota commented 3 years ago

Sry, @asterite what do you mean with "yield with an argument"? yield foo? I can't see what's confusing with this (or anything else we've talked about here).

asterite commented 3 years ago

The part where the block is assumed to be a macro. Usually when you call a method with a block, the block is regular Crystal, not macro code. It's very confusing.

straight-shoota commented 3 years ago

I see. Yes, that's indeed a bit confusing.

But short block syntax actually avoids that completely if it worked like I sketched above 😆 So if we were to deprecate full block syntax for macros with arguments, short block syntax could actually serve as a practical replacement, at least for some simple use cases.