{% skip_file if flag?(:without_interpreter) %}
require "./spec_helper"

describe Crystal::Repl::Interpreter do
  context "casts" do
    it "casts from reference to pointer and back" do
      interpret(<<-CRYSTAL).should eq("hello")
        x = "hello"
        p = x.as(UInt8*)
        y = p.as(String)
        y
      CRYSTAL
    end

    it "casts from reference to nilable reference" do
      interpret(<<-CRYSTAL).should eq("hello")
        x = "hello"
        y = x.as(String | Nil)
        if y
          y
        else
          "bye"
        end
      CRYSTAL
    end

    it "casts from mixed union type to another mixed union type for caller" do
      interpret(<<-CRYSTAL).should be_true
        a = 1 == 1 ? 1 : (1 == 1 ? 20_i16 : nil)
        if a
          a < 2
        else
          false
        end
      CRYSTAL
    end

    it "casts from nilable type to mixed union type" do
      interpret(<<-CRYSTAL).should eq(2)
        ascii = true
        delimiter = 1 == 1 ? nil : "foo"

        if ascii && delimiter
          1
        else
          2
        end
        CRYSTAL
    end

    it "casts from nilable type to mixed union type (2)" do
      interpret(<<-CRYSTAL).should be_true
        y = 1 == 1 ? "a" : nil
        x = true
        x = y
        x.is_a?(String)
      CRYSTAL
    end

    it "casts from mixed union type to primitive type" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq("2")
        x = 1 == 1 ? 2 : nil
        x.as(Int32)
      CRYSTAL
    end

    it "casts nilable from mixed union type to primitive type (non-nil case)" do
      interpret(<<-CRYSTAL).should eq(2)
        x = 1 == 1 ? 2 : nil
        y = x.as?(Int32)
        y ? y : 20
      CRYSTAL
    end

    it "casts nilable from mixed union type to primitive type (nil case)" do
      interpret(<<-CRYSTAL).should eq(20)
        x = 1 == 1 ? nil : 2
        y = x.as?(Int32)
        y ? y : 20
      CRYSTAL
    end

    it "upcasts between tuple types" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s)
        a =
          if 1 == 1
            {1, 'a'}
          else
            {true, 3}
          end

        a[0].as(Int32) + a[1].as(Char).ord
      CRYSTAL
    end

    it "upcasts between tuple types, respects alignment (#14036)" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq("123")
        a = {100, 20, 3}.as({Int32 | Int64, Int32, Int32})
        a[0].as(Int32) + a[1] + a[2]
      CRYSTAL
    end

    it "upcasts between named tuple types, same order" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s)
        a =
          if 1 == 1
            {a: 1, b: 'a'}
          else
            {a: true, b: 3}
          end

        a[:a].as(Int32) + a[:b].as(Char).ord
      CRYSTAL
    end

    it "upcasts between named tuple types, different order" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq((1 + 'a'.ord).to_s)
        a =
          if 1 == 1
            {a: 1, b: 'a'}
          else
            {b:3, a: true}
          end

        a[:a].as(Int32) + a[:b].as(Char).ord
      CRYSTAL
    end

    it "upcasts between named tuple types, respects alignment (#14036)" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq("123")
        a = {x: 100, y: 20_i64, z: 3}.as({y: Int64, z: Int32, x: Int32})
        a[:x] + a[:y] + a[:z]
      CRYSTAL
    end

    it "upcasts to module type" do
      interpret(<<-CRYSTAL).should eq(1)
        module Moo
        end

        class Foo
          include Moo

          def foo
            1
          end
        end

        class Bar
          include Moo

          def foo
            2
          end
        end

        moo = (1 == 1 ? Foo.new : Bar.new).as(Moo)
        if moo.is_a?(Foo)
          moo.foo
        else
          10
        end
      CRYSTAL
    end

    it "upcasts virtual type to union" do
      interpret(<<-CRYSTAL).should eq(2)
        class Foo
          def foo
            1
          end
        end

        class Bar < Foo
          def foo
            2
          end
        end

        foo = 1 == 1 ? Bar.new : Foo.new
        a = 1 == 1 ? foo : 10
        if a.is_a?(Foo)
          a.foo
        else
          20
        end
      CRYSTAL
    end

    it "casts nil to Void*" do
      interpret(<<-CRYSTAL).should eq(0)
        module Moo
          def self.moo(r)
            r.as(Void*)
          end
        end

        Moo.moo(nil).address
      CRYSTAL
    end

    it "does is_a? with virtual metaclass" do
      interpret(<<-CRYSTAL).should eq(1)
        class A
          def self.a
            2
          end
        end

        class B < A
          def self.b
            1
          end
        end

        class C < B
        end

        x = B || A
        if x.is_a?(B.class)
          x.b
        elsif x.is_a?(A.class)
          x.a
        else
          0
        end
        CRYSTAL
    end

    it "discards cast" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq("10")
        x = 1 || 'a'
        x.as(Int32)
        10
      CRYSTAL
    end

    it "raises when as fails" do
      interpret(<<-CRYSTAL, prelude: "prelude").to_s.should contain("Cast from Int32 to Char failed")
        x = 1 || 'a'
        begin
          x.as(Char)
          ""
        rescue ex : TypeCastError
          ex.message.not_nil!
        end
      CRYSTAL
    end

    it "casts to filtered type, not type in as(...)" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq("1")
        ({1} || 2).as(Tuple)[0]
      CRYSTAL
    end

    it "does is_a? with virtual type (struct)" do
      interpret(<<-CRYSTAL).should eq(10)
        abstract struct Foo
        end

        struct Bar < Foo
          def initialize(@x : Int32)
          end

          def bar
            @x
          end
        end

        struct Baz < Foo
          def initialize(@x : Int32)
          end

          def baz
            @x
          end
        end

        a = (Bar.new(10) || Baz.new(20)).as(Foo)
        case a
        when Bar
          a.bar
        when Baz
          a.baz
        else
          0
        end
        CRYSTAL
    end

    it "puts virtual metaclass into union (#12162)" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("ActionA"))
        class Action
        end

        class ActionA < Action
        end

        class ActionB < Action
        end

        x = ActionA || ActionB
        y = x || Nil
        y.to_s
        CRYSTAL
    end

    it "puts tuple type inside union of different tuple type (#12243)" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("{180}"))
        class A
          def initialize(@x : {Char | Int32}?)
          end

          def x
            @x
          end
        end

        x = A.new({180}).x
        x.to_s
      CRYSTAL
    end

    it "puts named tuple type inside union of different named tuple type (#12243)" do
      interpret(<<-CRYSTAL, prelude: "prelude").should eq(%("{v: 180}"))
        class A
          def initialize(@x : {v: Char | Int32}?)
          end

          def x
            @x
          end
        end

        x = A.new({v: 180}).x
        x.to_s
      CRYSTAL
    end

    it "casts from mixed union type to nilable proc type (#12283)" do
      interpret(<<-CRYSTAL).should eq("b")
        message = ->{ "b" }.as(String | Proc(String) | Nil)
        if message.is_a?(String)
          "a"
        elsif message.is_a?(Proc(String))
          message.call
        else
          "c"
        end
      CRYSTAL
    end

    it "does as? with no resulting type (#12327)" do
      interpret(<<-CRYSTAL).should eq(42)
        if nil.as?(Int32)
          0
        else
          42
        end
        CRYSTAL
    end

    it "does as? with no resulting type, not from nil (#12327)" do
      interpret(<<-CRYSTAL).should eq(42)
        if 1.as?(String)
          0
        else
          42
        end
        CRYSTAL
    end

    it "does as? with a type that can't match (#12346)" do
      interpret(<<-CRYSTAL).should eq(1)
        abstract class A
        end

        class B < A
        end

        class C < A
        end

        a = B.new || C.new
        a.as?(B | Int32) ? 1 : 2
        CRYSTAL
    end

    it "upcasts mixed union with tuple to mixed union with compatible tuple (1) (#12331)" do
      interpret(<<-CRYSTAL).should eq(1)
        class Foo
          def initialize(@tuple : Tuple(Int32?) | Tuple(Int32, Int32))
          end

          def tuple
            @tuple
          end
        end

        a = {1} || {1, 1}
        foo = Foo.new(a)
        tuple = foo.tuple
        if tuple.is_a?(Tuple(Int32?))
          value = tuple[0]
          if value
            value
          else
            2
          end
        else
          3
        end
      CRYSTAL
    end

    it "upcasts mixed union with tuple to mixed union with compatible tuple (2) (#12331)" do
      interpret(<<-CRYSTAL).should eq(2)
        class Foo
          def initialize(@tuple : Tuple(Int32?) | Tuple(Int32, Int32))
          end

          def tuple
            @tuple
          end
        end

        a = {nil} || {1, 1}
        foo = Foo.new(a)
        tuple = foo.tuple
        if tuple.is_a?(Tuple(Int32?))
          value = tuple[0]
          if value
            value
          else
            2
          end
        else
          3
        end
      CRYSTAL
    end

    it "upcasts mixed union with tuple to mixed union with compatible tuple (3) (#12331)" do
      interpret(<<-CRYSTAL).should eq(3)
        class Foo
          def initialize(@tuple : Tuple(Int32?) | Tuple(Int32, Int32))
          end

          def tuple
            @tuple
          end
        end

        a = {1, 1} || {1}
        foo = Foo.new(a)
        tuple = foo.tuple
        if tuple.is_a?(Tuple(Int32?))
          value = tuple[0]
          if value
            value
          else
            2
          end
        else
          3
        end
      CRYSTAL
    end

    it "upcasts in nilable cast (#12532)" do
      interpret(<<-CRYSTAL).should eq(2)
        struct Nil
          def foo
            0
          end
        end

        module A
          def foo
            1
          end
        end

        class B
          include A

          def foo
            2
          end
        end

        class C
          include A
        end

        B.new.as?(A).foo
        CRYSTAL
    end

    it "upcasts GenericClassInstanceMetaclassType to VirtualMetaclassType" do
      interpret(<<-CRYSTAL).should eq(2)
        class Foo
          def self.foo; 1; end
        end

        class Gen(T) < Foo
          def self.foo; 2; end
        end

        Gen(Int32).as(Foo.class).foo
        CRYSTAL
    end
  end
end
