[RFC] Annotations metadata declaration/DSL

Opened RFC: Improved Annotations by Blacksmoke16 · Pull Request #17 · crystal-lang/rfcs · GitHub for more focused feedback on my proposal.


Yea would def be a good point to re-vamp the docs/add some more examples. Esp the API docs. I think there should also be some new page/guide/something in the crystal book that serves as an intro to the macro API outside of a macro definition. I.e. better explains the macro AST setup and how to do common things like this.

Here’s a bit of a brain dump for future reference/inclusion in docs:

Are you saying you have something like:

annotation MyAnn; end

module MyApp
  module Foo
    @[MyAnn]
    record One
    record Two

    @[MyAnn]
    record Three
  end

  module Bar
    record Three

    @[MyAnn]
    record Four

    record Five
  end
end

and you’re wanting to only select the types within MyApp::Foo with a specific annotation? So [One, Three] in this example?

I’m not sure of a great way to do namespace filtering. Like you can do {% MyApp.constants %} and get [Foo, Bar] but they’re macro ID’s and can’t really do much with them. Could be nice to expose child namespaces that give you TypeNode instances. Something a bit more robust than like:

MyApp.constants.map { |c| parse_type("MyApp::#{c}").resolve }

Normally, at least how I been using annotations, is you start off by casting a wide net and then filter it down based on other criteria. Some examples of this are like:

  • All classes/structs
  • All subclasses of some specific type
  • All types that include a specific module
  • Some combination of the above

From here you could then go from “all types in the entire program” to “all types in the entire program with a specific annotation.” For example, getting all deprecated class/structs in the stdlib:

{%
 Object.all_subclasses.select(&.annotation(Deprecated)) # => [Atomic::Flag, Float::Printer::DiyFP]
%}

Once you have all your root data, you could then use additional #select calls to further filter the array down. So in this example could do something like:


{%
  Object
    .all_subclasses                            # Iterate thru all class/struct types
    .select(&.annotation(MyAnn))               # Filter down to only those with `@[MyAnn]`
    .select(&.name.starts_with?("MyApp::Foo")) # Filter down to only those in the `MyApp::Foo` namespace
%}                                             # => [MyApp::Foo::One, MyApp::Foo::Three]

You could then do something similar and iterate over each of the #methods in each type to do more filtering to get down to only the Def instances you care about.