The Crystal Programming Language Forum

Iterating through the types of a union type

I have this union type defined:

module Hierarchical
  extend self
  alias Types = Folder | Forum | Language | Manufacturer | Product | Topic | Page | Post
end

I would like to iterate through the types in a macro, so that I iterate upon Folder, Forum, …
I couldn’t quite figure out how to write that from the docs. It would be something like

{% for type in Hierarchical::Types.union_types %}
  do_something_with( type.name )
{% end %}

but that’s not working. On the first iteration I get the name of the common superclass of all of my union types, with a “+” at the end.

Thanks

Bruce

The compiler merges unions of types that inherit from Superclass as Superclass+. This is typically useful to reduce the amount of overall types the compiler needs to keep track of when those unions just come together by chance (for example Folder.new || Forum.new).

This is of course unexpected behaviour when there’s an explicit definition link in your case and you expect to get the actual values you specified. There’s an open issue about that (or rather a related case, alias isn’t mentioned explicitly) at https://github.com/crystal-lang/crystal/issues/9050 and a PR at https://github.com/crystal-lang/crystal/pull/9052

This should get you on the right track:

abstract struct Foo
end

{% for type in %w[Folder Forum Language Manufacturer Product Topic Page Post] %}
  struct {{type.id}} < Foo
  end
{% end %}

module Hierarchical
  extend self
  alias Types = Folder | Forum | Language | Manufacturer | Product | Topic | Page | Post
end

{% for type in Hierarchical::Types.union_types %}
  {% if type.subclasses.any? { |s| s } %}
   {% for subtype in type.subclasses %}
     {% pp subtype %}
   {% end %}
  {% else %}
    {% pp type %}
  {% end %}
{% end %}

It works with and without the inheritance, you’ll just need to make sure you do the same thing with subtype as with type if you want to support both, maybe by extracting the behavior to a macro.

1 Like

This is an entirely different angle. Your example prints all subclasses of Foo, even those not included in Hierarchical::Types.
If you add struct Bar < Foo; end to the code, it get’s listed as members of Hierarchical::Type. This techincally correctly reflects how the compiler treats this union, but it probably doesn’t fit the intended use case of that union. If it did, there would be no reason to list the sublasses explicitly as union members, you could just use the superclass (either directly or as a union with non-inherited types).

1 Like

Ah, good point

A tuple that holds all the type classes can be used:

class A
end

class B < A
end

module Hierarchical
  extend self
  {% begin %}
  {% types = %w(A B)%}
  alias Types = {{types.join(" | ").id}}
  TypesTuple = { {{types.join(", ")}} }
  {% end %}
end

{% for type in Hierarchical::TypesTuple %}
  puts {{type}}
{% end%}