require "../../spec_helper"

describe "Code gen: union type" do
  it "codegens union type when obj is union and no args" do
    run("a = 1; a = 2.5_f32; a.to_f").to_f64.should eq(2.5)
  end

  it "codegens union type when obj is union and arg is union" do
    run("a = 1; a = 1.5_f32; (a + a).to_f").to_f64.should eq(3)
  end

  it "codegens union type when obj is not union but arg is" do
    run("a = 1; b = 2; b = 1.5_f32; (a + b).to_f").to_f64.should eq(2.5)
  end

  it "codegens union type when obj union but arg is not" do
    run("a = 1; b = 2; b = 1.5_f32; (b + a).to_f").to_f64.should eq(2.5)
  end

  it "codegens union type when no obj" do
    run("def foo(x); x; end; a = 1; a = 2.5_f32; foo(a).to_f").to_f64.should eq(2.5)
  end

  it "codegens union type when no obj and restrictions" do
    run("def foo(x : Int); 1.5; end; def foo(x : Float); 2.5; end; a = 1; a = 3.5_f32; foo(a).to_f").to_f64.should eq(2.5)
  end

  it "codegens union type as return value" do
    run("def foo; a = 1; a = 2.5_f32; a; end; foo.to_f").to_f64.should eq(2.5)
  end

  it "codegens union type for instance var" do
    run(<<-CRYSTAL).to_f64.should eq(3)
      struct Float
        def &+(other)
          self + other
        end
      end

      struct Int32
        def &+(other : Float)
          self + other
        end
      end

      class Foo
        @value : Int32 | Float32

        def initialize(value)
          @value = value
        end
        def value=(@value); end
        def value; @value; end
      end

      f = Foo.new(1)
      f.value = 1.5_f32
      (f.value &+ f.value).to_f
      CRYSTAL
  end

  it "codegens if with same nested union" do
    run(<<-CRYSTAL).to_i.should eq(1)
      if true
        if true
          1
        else
          2.5_f32
        end
      else
        if true
          1
        else
          2.5_f32
        end
      end.to_i!
      CRYSTAL
  end

  it "assigns union to union" do
    run(<<-CRYSTAL).to_i.should eq(97)
      require "prelude"

      struct Nil; def to_i; 0; end; end

      struct Char
        def to_i
          ord
        end
      end

      class Foo
        @x : Int32 | Char | Nil

        def foo(x)
          @x = x
          @x = @x || 1
        end

        def x
          @x
        end
      end

      f = Foo.new
      f.foo 1
      f.foo 'a'
      f.x.to_i
      CRYSTAL
  end

  it "assigns union to larger union" do
    run(<<-CRYSTAL).to_string.should eq("d")
      require "prelude"
      a = 1
      a = 1.1_f32
      b = "c"
      b = 'd'
      a = b
      a.to_s
      CRYSTAL
  end

  it "assigns union to larger union when source is nilable 1" do
    value = run(<<-CRYSTAL).to_string
      require "prelude"
      a = 1
      b = nil
      b = Reference.new
      a = b
      a.to_s
      CRYSTAL
    value.should contain("Reference")
  end

  it "assigns union to larger union when source is nilable 2" do
    run(<<-CRYSTAL).to_string.should eq("")
      require "prelude"
      a = 1
      b = Reference.new
      b = nil
      a = b
      a.to_s
      CRYSTAL
  end

  it "dispatch call to object method on nilable" do
    run(<<-CRYSTAL)
      require "prelude"
      class Foo
      end

      a = nil
      a = Foo.new
      a.nil?
      CRYSTAL
  end

  it "sorts restrictions when there are unions" do
    run(<<-CRYSTAL).to_i.should eq(2)
      class Middle
      end

      class Top < Middle
      end

      class Another1
      end

      class Another2
      end

      def type_id(type : Another2)
        1
      end

      def type_id(y : Top)
        2
      end

      def type_id(y : Middle | Another1)
        3
      end

      def type_id(y)
        4
      end

      t = Top.new || Another1.new
      type_id t
      CRYSTAL
  end

  it "codegens union to_s" do
    str = run(<<-CRYSTAL).to_string
      require "prelude"

      def foo(x : T) forall T
        T.to_s
      end

      a = 1 || 1.5
      foo(a)
      CRYSTAL
    str.in?("(Int32 | Float64)", "(Float64 | Int32)").should be_true
  end

  it "provides T as a tuple literal" do
    run(<<-CRYSTAL).to_string.should eq("TupleLiteral")
      struct Union
        def self.foo
          {{ T.class_name }}
        end
      end
      Union(Nil, Int32).foo
      CRYSTAL
  end

  it "respects union payload alignment when upcasting Bool (#14898)" do
    mod = codegen(<<-CRYSTAL)
      x = uninitialized Bool | UInt8[64]
      x = true
      CRYSTAL

    str = mod.to_s
    {% if LibLLVM::IS_LT_150 %}
      str.should match(/store i512 1, i512\* %\d+, align 8/)
    {% else %}
      str.should match(/store i512 1, ptr %\d+, align 8/)
    {% end %}

    # an i512 store defaults to 16-byte alignment, which is undefined behavior
    # as it overestimates the actual alignment of `x`'s data field (x86 in
    # particular segfaults on misaligned 16-byte stores)
    str.should_not contain("align 16")
  end
end
