crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.26k stars 1.61k forks source link

Recursive type alias leads to infinite loop #12100

Open dorianmariecom opened 2 years ago

dorianmariecom commented 2 years ago

I tried to do:

    alias Output = String |
                   Hash(Symbol, Output) |
                   Hash(Symbol, Hash(Symbol, Output))

Because I wanted to do @output = { name => @output }.

That lead to:

[0x1047ffe14] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2192 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x104809a0c] *Crystal::Type+@Crystal::Type#restrict<Crystal::UnionType+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +440 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047fff54] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2512 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047c7e4c] *Crystal::UnionType+@Crystal::UnionType#restrict_type_or_fun_or_generic<Crystal::Type+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +576 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ffe14] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2192 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x104809a0c] *Crystal::Type+@Crystal::Type#restrict<Crystal::UnionType+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +440 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047fff54] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2512 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047c7e4c] *Crystal::UnionType+@Crystal::UnionType#restrict_type_or_fun_or_generic<Crystal::Type+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +576 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ffe14] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2192 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x104809a0c] *Crystal::Type+@Crystal::Type#restrict<Crystal::UnionType+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +440 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047fff54] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2512 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047c7e4c] *Crystal::UnionType+@Crystal::UnionType#restrict_type_or_fun_or_generic<Crystal::Type+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +576 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ffe14] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2192 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x104809a0c] *Crystal::Type+@Crystal::Type#restrict<Crystal::UnionType+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +440 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047fff54] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2512 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047c7e4c] *Crystal::UnionType+@Crystal::UnionType#restrict_type_or_fun_or_generic<Crystal::Type+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +576 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ffe14] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict_type_var<Crystal::ASTNode+, Crystal::ASTNode+, Crystal::MatchContext>:(Bool | Crystal::NumberLiteral | Nil) +2192 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x1047ff1bc] *Crystal::GenericInstanceType+@Crystal::GenericInstanceType#restrict<Crystal::GenericInstanceType+, Crystal::MatchContext>:(Crystal::GenericInstanceType+ | Nil) +368 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
[0x104809a0c] *Crystal::Type+@Crystal::Type#restrict<Crystal::UnionType+, Crystal::MatchContext>:(Crystal::Type+ | Nil) +440 in /Users/dorianmariefr/.asdf/installs/crystal/1.4.1/embedded/bin/crystal
~/s/language> crystal version
Crystal 1.4.1 [b7377c041] (2022-04-22)

LLVM: 10.0.0
Default target: aarch64-apple-darwin

https://github.com/dorianmariefr/language/tree/crystal-recursive-type

straight-shoota commented 2 years ago

Recursive aliases are broken in many ways (ref #5155). This is nevertheless a valid compiler bug. The compiler should not segfault on a recursive alias. But it's probably never going to work.

I'd strongly advise against using them. They can usually be very easily replaced by a simple struct wrapper (see #5155 and https://github.com/crystal-lang/crystal/pull/5183 for an example).

dorianmariecom commented 2 years ago

Thanks, I basically copied the json module and did:

class Language
  class Output
    def_clone

    alias Type = String | Hash(Symbol, Output)

    getter raw : Type

    def initialize(@raw : Type = "")
    end

    def ==(other : Output)
      raw == other.raw
    end

    def ==(other)
      raw == other
    end

    def pretty_print(pp)
      raw.pretty_print(pp)
    end

    def to_s(io)
      raw.to_s(io)
    end

    def inspect(io)
      raw.inspect(io)
    end
  end
end

class Object
  def ===(other : Language::Output)
    self === other.raw
  end
end

struct Value
  def ==(other : Language::Output)
    self == other.raw
  end
end

class Reference
  def ==(other : Language::Output)
    self == other.raw
  end
end

class Array
  def ==(other : Language::Output)
    self == other.raw
  end
end

class Hash
  def ==(other : Language::Output)
    self == other.raw
  end
end

class Regex
  def ===(other : Language::Output)
    value = self === other.raw
    $~ = $~
    value
  end
end

And when I use it:

    def aka(name)
      if @buffer.empty?
        @output = Output.new({name => @output})
      else
        @output = Output.new({name => Output.new(@buffer)})
        @buffer = ""
      end
    end

I still think it would be simpler if recursive types were properly supported