Compile time / macro for generic type deduction?

This is related to How to create a union type out of an array literal?

My original issue started out with a generic table that was supposed to get a (parametrized) reference type in addition. When I added the type, the compiler started complaining (two below), rightly so.

Since I was not able to deduce the underlying base type BaseCell inside Table(T) with the help of macros, I started passing both types to the generic class; which works, but somehow feels unwieldy.

In my non-reduced code I need even more generic types, so it gets kind of confusing.

I wonder if I miss some more elegant way…

# e.g. `crystal run -Done reference.cr`

{% if flag?(:one) %}

# this works

class Table(T)
    @value : T|Nil = nil
    def m
        @value = ReferenceCell.new
    end
end

class ReferenceCell
end

alias BaseCell = Int32|Nil
alias Cell = BaseCell|ReferenceCell

t = Table(Cell).new
t.m

{% end %}

{% if flag?(:two) %}

# this doesn't work because of the wrong usage of type recursion

class Table(T)
    @value : T|Nil = nil
    def m
        @value = ReferenceCell(T).new # T is wrong here, would need BaseCell instead
# Error: instance variable '@value' of Table(Int32 | ReferenceCell(Int32 | Nil) | Nil)
# must be                (Int32 | ReferenceCell(Int32 | Nil) | Nil)
# not       ReferenceCell(Int32 | ReferenceCell(Int32 | Nil) | Nil)
    end
end

class ReferenceCell(T)
end

alias BaseCell = Int32|Nil
alias Cell = BaseCell|ReferenceCell(BaseCell)

t = Table(Cell).new
t.m

{% end %}

{% if flag?(:three) %}

# works again

class Table(T,U) # outward type, inner reference type
    @value : T|Nil = nil
    def m
        @value = ReferenceCell(U).new
    end
end

class ReferenceCell(T)
end

alias BaseCell = Int32|Nil
alias Cell = BaseCell|ReferenceCell(BaseCell)

t = Table(Cell, BaseCell).new
t.m

{% end %}

Can U always be inferred from T? An example:

class Table(T)
  @value : T?

  def m
    @value = ReferenceCell(typeof(begin
               x = uninitialized T
               x.is_a?(ReferenceCell) ? raise("") : x
             end)).new
  end
end

alias BaseCell = Int32 | Nil
alias Cell = BaseCell | ReferenceCell(BaseCell)

class ReferenceCell(T)
end

t = Table(Cell).new
typeof(t.m) # => ReferenceCell(Int32 | Nil)

Thanks, this works! :slight_smile:
But it cannot be put in an alias, so the code suffers anyhow. :frowning: