Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unrestricted block arguments are Nop in macros #5334

Open
willhbr opened this issue Nov 29, 2017 · 4 comments
Open

Unrestricted block arguments are Nop in macros #5334

willhbr opened this issue Nov 29, 2017 · 4 comments
Labels
help wanted This issue is generally accepted and needs someone to pick it up kind:feature topic:compiler topic:stdlib:macros

Comments

@willhbr
Copy link
Contributor

willhbr commented Nov 29, 2017

The .block_arg macro method on method declarations only returns an Arg if the block has a type restriction. If there is no restriction then Nop is returned.

For example, this should print true, true, false, false. Currently it prints true, true, true, false.

class Foo
  macro finished
    {% for method in @type.methods %}
      {% puts method.block_arg.is_a? Nop %}
    {% end %}
  end

  def no_block
  end

  def just_yield
    yield 4
  end

  def yield_and_block(&block)
    yield 4
  end

  def yield_and_block_with_type(&block : Int32 ->)
    yield 4
  end
end

By removing the if statement in the Def normalizer this works as expected - the last two methods have a block_arg.

However, instead of fixing this, I think a better move would be to make an accepts_block? macro method that checks whether the method has a yield or a &block arg. Unless there is a need for getting the name of the block arg?

This is on master (and is the same in 0.23.1, fwiw) on Ubuntu 16.04

$ crystal --version
Crystal 0.24.0+70 [1978c8b] (2017-11-29)
LLVM: 3.8.0
Default target: x86_64-pc-linux-gnu
@maiha
Copy link
Contributor

maiha commented Feb 12, 2018

I also facing same problem when fetching an information of block is need? or not.
For example, we expect true but got false in foo1.

class Foo
  def foo1
    yield
  end

  def foo2(&block)
    block.call
  end

  {% for m in @type.methods %}
    {% p [m.name, :block, !! m.block_arg] %}
  {% end %}
end
[foo1, :block, false]
[foo2, :block, true]
  • crystal-0.24.1

How can I know the fact that foo1 needs block in macros?
Best regards,

@asterite
Copy link
Member

In the implementation we have a property for that, it's called yields. Maybe we can expose it, I don't know of a use case, though.

@bew
Copy link
Contributor

bew commented Feb 12, 2018

A use case would be to generate only one method in the macro Object.delegate instead of 2 when the delegated method yields/doesn't yield:

crystal/src/object.cr

Lines 1083 to 1095 in 84fa9ec

macro delegate(*methods, to object)
{% for method in methods %}
def {{method.id}}(*args, **options)
{{object.id}}.{{method.id}}(*args, **options)
end
def {{method.id}}(*args, **options)
{{object.id}}.{{method.id}}(*args, **options) do |*yield_args|
yield *yield_args
end
end
{% end %}
end

@HertzDevil
Copy link
Contributor

According to the normalizer a bare -> also gets stripped:

class Foo
  macro finished
    {% for method in @type.methods %}
      {% puts method.block_arg.is_a? Nop %} # => true
    {% end %}
  end

  def yield_and_block_with_type(&block : ->)
    yield
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted This issue is generally accepted and needs someone to pick it up kind:feature topic:compiler topic:stdlib:macros
Projects
None yet
Development

No branches or pull requests

6 participants