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

describe Crystal::Repl::Interpreter do
  context "multidispatch" do
    it "does dispatch on one argument" do
      interpret(<<-CRYSTAL).should eq(42)
        def foo(x : Char)
          x.ord.to_i32
        end

        def foo(x : Int32)
          x
        end

        a = 42 || 'a'
        foo(a)
      CRYSTAL
    end

    it "does dispatch on one argument inside module with implicit self" do
      interpret(<<-CRYSTAL).should eq(42)
        module Moo
          def self.foo(x : Char)
            x.ord.to_i32
          end

          def self.foo(x : Int32)
            x
          end

          def self.bar
            a = 42 || 'a'
            foo(a)
          end
        end

        Moo.bar
      CRYSTAL
    end

    it "does dispatch on one argument inside module with explicit receiver" do
      interpret(<<-CRYSTAL).should eq(42)
        module Moo
          def self.foo(x : Char)
            x.ord.to_i32
          end

          def self.foo(x : Int32)
            x
          end

          def self.bar
          end
        end

        a = 42 || 'a'
        Moo.foo(a)
      CRYSTAL
    end

    it "does dispatch on receiver type" do
      interpret(<<-CRYSTAL).should eq(42)
        struct Char
          def foo
            self.ord.to_i32
          end
        end

        struct Int32
          def foo
            self
          end
        end

        a = 42 || 'a'
        a.foo
      CRYSTAL
    end

    it "does dispatch on receiver type and argument type" do
      interpret(<<-CRYSTAL).should eq(42 + 'b'.ord)
        struct Char
          def foo(x : Int32)
            self.ord.to_i32 + x
          end

          def foo(x : Char)
            self.ord.to_i32 + x.ord.to_i32
          end
        end

        struct Int32
          def foo(x : Int32)
            self + x
          end

          def foo(x : Char)
            self + x.ord.to_i32
          end
        end

        a = 42 || 'a'
        b = 'b' || 43
        a.foo(b)
      CRYSTAL
    end

    it "does dispatch on receiver type and argument type, multiple times" do
      interpret(<<-CRYSTAL).should eq(2 * (42 + 'b'.ord))
        struct Char
          def foo(x : Int32)
            self.ord.to_i32 + x
          end

          def foo(x : Char)
            self.ord.to_i32 + x.ord.to_i32
          end
        end

        struct Int32
          def foo(x : Int32)
            self + x
          end

          def foo(x : Char)
            self + x.ord.to_i32
          end
        end

        a = 42 || 'a'
        b = 'b' || 43
        x = a.foo(b)
        y = a.foo(b)
        x + y
      CRYSTAL
    end

    it "does dispatch on one argument with struct receiver, and modifies it" do
      interpret(<<-CRYSTAL).should eq(32)
        struct Foo
          def initialize
            @x = 2_i64
          end

          def foo(x : Int32)
            v = @x + x
            @x = 10_i64
            v
          end

          def foo(x : Char)
            v = @x + x.ord.to_i32
            @x = 30_i64
            v
          end

          def x
            @x
          end
        end

        foo = Foo.new

        a = 20 || 'a'
        b = foo.foo(a)
        b + foo.x
      CRYSTAL
    end

    it "downcasts self from union to struct (pass pointer to self)" do
      interpret(<<-CRYSTAL).should eq(2)
        class Foo
          def initialize
            @x = 1_i64
          end

          def x
            @x
          end
        end

        struct Point
          def initialize
            @x = 2_i64
          end

          def x
            @x
          end
        end

        obj = Point.new || Foo.new
        obj.x
      CRYSTAL
    end

    it "does dispatch on virtual type" do
      interpret(<<-CRYSTAL).should eq(4)
        abstract class Foo
          def foo
            1
          end
        end

        class Bar < Foo
        end

        class Baz < Foo
          def foo
            3
          end
        end

        class Qux < Foo
        end

        foo = Bar.new || Baz.new
        x = foo.foo

        foo = Baz.new || Bar.new
        y = foo.foo

        x + y
      CRYSTAL
    end

    it "does dispatch on one argument with block" do
      interpret(<<-CRYSTAL).should eq(42)
        def foo(x : Char)
          yield x.ord.to_i32
        end

        def foo(x : Int32)
          yield x
        end

        a = 32 || 'a'
        foo(a) do |x|
          x + 10
        end
      CRYSTAL
    end

    it "doesn't compile block if it's not used (no yield)" do
      interpret(<<-CRYSTAL).should eq(2)
        class Object
          def try
            yield self
          end
        end

        struct Nil
          def try(&)
            self
          end
        end

        a = 1 || nil
        b = a.try { |x| x + 1 }
        b || 10
      CRYSTAL
    end

    it "does multidispatch on virtual metaclass type (1)" do
      interpret(<<-CRYSTAL).should eq("BB")
        class Class
          def lt(other : T.class) : String forall T
            {% @type %}
            other.gt(self)
          end

          def gt(other : T.class) forall T
            {{ @type.stringify + T.stringify }}
          end
        end

        class A
        end

        class B < A
        end

        t = B || A
        t.lt(t)
      CRYSTAL
    end

    it "does multidispatch on virtual metaclass type (2)" do
      interpret(<<-CRYSTAL).should eq("BB")
        class Class
          def lt(other : T.class) : String forall T
            {% @type %}
            other.gt(self)
          end

          def gt(other : T.class) forall T
            {{ @type.stringify + T.stringify }}
          end
        end

        class A
        end

        class B < A
        end

        class C < B
        end

        t = B || A
        t.lt(t)
      CRYSTAL
    end

    it "passes self as pointer when doing multidispatch" do
      interpret(<<-CRYSTAL).should eq(10)
        struct Foo
          def initialize(@x : Int32)
          end

          def x
            @x
          end

          def to_unsafe
            pointerof(@x)
          end
        end

        struct Bar
          def initialize(@x : Int32)
          end

          def x
            @x
          end

          def to_unsafe
            pointerof(@x)
          end
        end

        foo = Foo.new(0) || Bar.new(1)
        foo.to_unsafe.value = 10
        foo.x
      CRYSTAL
    end

    it "passes self as pointer when doing multidispatch (2)" do
      interpret(<<-CRYSTAL).should be_true
        struct Tuple
          def ==(other)
            false
          end
        end

        a = 1.as(Int32 | Tuple(Int64, Int64))
        a == 1
      CRYSTAL
    end

    it "initialize multidispatch" do
      interpret(<<-CRYSTAL).should eq(1)
        struct Foo
          def initialize(x : Int64)
            initialize(x, 1 || 'a')
          end

          def initialize(@x : Int64, y : Int32)
          end

          def initialize(@x : Int64, y : Char)
          end

          def x
            @x
          end
        end

        Foo.new(1_i64).x
      CRYSTAL
    end

    it "does multidispatch with mandatory named arguments" do
      interpret(<<-CRYSTAL).should eq(1)
        class Object
          def foo(obj, *, file = "")
            obj
          end
        end

        ("" || nil).foo 1, file: ""
      CRYSTAL
    end

    it "does multidispatch with captured block (#12217)" do
      interpret(<<-CRYSTAL).should eq(42)
        class A
          def then(&callback : Int32 -> Int32)
            callback.call(70)
          end
        end

        class B < A
          def then(&callback : Int32 -> Int32)
            callback.call(30)
          end
        end

        a = A.new
        b = B.new

        a_value = (a || b).then do |x|
          x + 3
        end

        b_value = (b || a).then do |x|
          x + 1
        end

        a_value - b_value
      CRYSTAL
    end

    it "casts multidispatch argument to the def's arg type" do
      interpret(<<-CRYSTAL)
        def foo(a : String) forall T
        end

        def foo(a)
          a
        end

        foo("b" || nil)
      CRYSTAL
    end

    it "doesn't reuse a cached dispatch chain when a later call site sees a wider target_defs set (#16810)" do
      # Two call sites against the same union type. The second site
      # instantiates `G(C)`, which adds an extra `target_def` to the
      # multidispatch. With the old cache key (`obj_type` + `signature`
      # only) both sites would share a dispatch chain built for the
      # first one, and the new type would fall through into the
      # `unreachable` else branch.
      interpret(<<-CRYSTAL).should eq(42 * 1000 + 42)
        module M1; end
        module M2; end

        class G(T)
          include M1
          def my_check
            42
          end
        end

        alias V = Int32 | M1 | M2 | Nil

        class C
          include M2
        end

        class Object
          def my_check
            0
          end
        end

        v1 = G(M2 | C).new.as(V)
        res1 = v1.my_check

        v2 = G(C).new.as(V)
        res2 = v2.my_check

        res1 &* 1000 &+ res2
      CRYSTAL
    end
  end
end
