Need help debugging a macro please

Hello Group! Newbie here. I was playing around trying to create a MVC like framework but I’m having difficulties trying to get this work. Can someone explain what’s wrong here?

annotation Route
end

macro setup_handlers
    {% for m in @type.methods %}
        {% if a = m.annotation(Route) %}
            if path == {{a.args[0]}}
                puts "executing {{m.name}} on {{@type.name}}"
                {{m.name}}()
                return
            end
        {% end %}
    {% end %}
    {% debug %}
end

abstract class Controller
  def call(path)
    setup_handlers
  end
end

class Foo < Controller
  @[Route("/info")]
  def info
    puts "in B::info"
  end
end

class Bar < Foo
  @[Route("/info")]
  def info
    puts "in C::info"
  end
end

Bar.new.call("/info")

This works as expected but if I remove the info() from Bar I’d expect it to find “/info” in the parent class, Foo but it doesn’t! Any idea what I’m doing wrong here?

Also, how do I ensure that the most derived methods show up first in the search (in setup_handlers) I see the @type.methods don’t take any arguments!

Thanks!

Hi! #methods doesn’t include methods from parent types.

You can do this:

macro setup_handlers
  {% for type in [@type] + @type.ancestors %}
    {% for m in type.methods %}
      {% if a = m.annotation(Route) %}
        if path == {{a.args[0]}}
          puts "executing {{m.name}} on {{type.name}}"
          {{m.name}}()
          return
        end
      {% end %}
    {% end %}
  {% end %}
  {% debug %}
end

It would probably be nice to have a simpler way to list all methods, including those from ancestors. Maybe in the future? :-)

2 Likes

I made an issue for that :slight_smile:.

Interesting to see someone else go the annotation route. I’m curious to see how it turns out.

1 Like

Works like a charm! Thanks a bunch @asterite! On a different note, is there any reason why a macro would work when embedded inline but not work when defined as a macro by itself?

That’s neat! Thanks @blacksmoke16! Well, I hope I can positively contribute something to the Crystal community! You guys are awesome!

1 Like

Could you show some code? I don’t understand what this means

Sure, here it is. This as is, refuses to work, ie, no handlers are attached for some reason (won’t print any “found x…” as well), debug comes up empty.

macro setup_handlers
  {% for type in [@type] + @type.ancestors %}
      {% for m in type.methods %}
        {% if a = m.annotation(Route) %}
          if path == {{a.args[0]}}
            {{puts "found #{m.name} on #{type.name}"}}
            {{m.name}}(env)
            return
          end
        {% end %}
      {% end %}
    {% end %}
  {% debug %}
end

abstract class Controller
  include HTTP::Handler

  def call(env)
    setup_handlers
    call_next(env)
  end
end

but if I paste the whole macro body inline like so, it seems to work!

abstract class Controller
  include HTTP::Handler

  def call(env)
    {% for type in [@type] + @type.ancestors %}
      {% for m in type.methods %}
        {% if a = m.annotation(Route) %}
          if path == {{a.args[0]}}
            {{puts "executing #{m.name} on #{type.name}"}}
            puts "executing {{m.name}} on {{type.name}}"
            {{m.name}}(env)
            return
          end
        {% end %}
      {% end %}
    {% end %}
    call_next(env)
  end
end

Try doing:

macro setup_handlers
  {% verbatim do %}
    {% for type in [@type] + @type.ancestors %}
        {% for m in type.methods %}
          {% if a = m.annotation(Route) %}
            if path == {{a.args[0]}}
              {{puts "found #{m.name} on #{type.name}"}}
              {{m.name}}(env)
              return
            end
          {% end %}
        {% end %}
      {% end %}
    {% debug %}
  {% end %}
end

verbatim do escapes the macro code so that i’ll evaluate in the scope that the macro is used, i.e. in your call method. Currently its being evaluated within the macro itself, which doesn’t have any methods/parent types.

Right, when you call setup_handlers manually, the @type is just the top-level type, not the type where the macro is called.

There’s a discussion about whether that’s okay or not, but we can’t change it right now because it’s a breaking change. There should be a way to get the type in the scope and the type where the macro is declared.

Ah that’s good to know, I’ll give it a try! Thank you!

Thanks for the background – I think it feels more natural to have the macro expand in the lexical scope as is, I think that’s what LISP does if I’m not mistaken – it’s nice to know there’s on-going discussion about this!