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.