krzysztofzablocki / Sourcery

Meta-programming for Swift, stop writing boilerplate code.
http://merowing.info
MIT License
7.59k stars 605 forks source link

Add more Stencil nodes (to be able to do stuff like enum.allValues) #5

Closed AliSoftware closed 7 years ago

AliSoftware commented 7 years ago

As discussed in Slack, we definitely need to move the Stencil nodes added in SwiftGen to make them available into Stencil proper, or at least Insanity should merge them. This issue is just to keep track of progress on that matter.

For now, there's too many stuff that are not possible with Insanity due to Stencil limitations in nodes.


For example, I wanted to add a template that would generate allValues for each enum. Currently, this is not possible, because:

  1. We need to generate allValues only if the enum has no case with an associated value: if it has at least one case with an AV that won't make sense to generate allValues. But there's currently no way in Stencil to test if "any item in that array has that property":
    • enum.cases.hasAssociatedValue doesn't work because enum.cases is an array,
    • there is no map or reduce or any Node in Stencil
    • We could do it with a for loop and {% set hasAV %}1{% endset %} if one of the iterated case hasAssociatedValue so that we could then {% if hasAV %} afterwards, but the set node isn't part of Stencil yet either
  2. We need to generate allValues only if the enum isn't generic, as static var for generic types are not yet supported. Currently the Stencil context doesn't expose if the type is generic (there's no field in the Stencil Context telling that type.isGeneric for example)
    • For our purposes that's not that problematic because a generic enum like Result<T> will likely use T as a type of one of its associated types anyway, so if we manage to solve 1 that won't be a blocker for our allValues case, but still.

For lack of a better solution, this works for enums with no associated values, but omit cases with associated values when there is one (instead of not generating the allValues property at all. But it still generates an invalid code for generic enums anyway.

{% for enum in types.enums %}
extension {{ enum.name }} {
  static var allValues: [{{enum.name}}] = [
    {% for case in enum.cases %}{% if not case.hasAssociatedValue %}.{{case.name}}, {%endif%}{% endfor %}
  ]
}
{% endfor %}
krzysztofzablocki commented 7 years ago

Getting a separate framework with Stencil extensions that we could share between SwiftGen and Insanity is definitely something we should work on soon.

Due to how I structured this code adding more reflection like hasAssociatedValue or isGeneric into Enum / Type is trivial, it's just a computed property that we need to add and it will be available in Stencil templates immediately

krzysztofzablocki commented 7 years ago

I've added hasAssociatedValues to Enum introspection, closing this task as we discussed it off github and we'll make task of extracting Stencil extensions that Olivier uses in his repo first, then we'll integrate it with Insanity.