require "../../spec_helper"

describe "Semantic: virtual" do
  it "types two classes without a shared virtual" do
    assert_type(<<-CRYSTAL) { union_of(types["Foo"], types["Bar"]) }
      class Foo
      end

      class Bar
      end

      a = Foo.new || Bar.new
      CRYSTAL
  end

  it "types class and subclass as one type" do
    assert_type(<<-CRYSTAL) { types["Foo"].virtual_type }
      class Foo
      end

      class Bar < Foo
      end

      a = Foo.new || Bar.new
      CRYSTAL
  end

  it "types two subclasses" do
    assert_type(<<-CRYSTAL) { types["Foo"].virtual_type }
      class Foo
      end

      class Bar < Foo
      end

      class Baz < Foo
      end

      a = Bar.new || Baz.new
      CRYSTAL
  end

  it "types class and two subclasses" do
    assert_type(<<-CRYSTAL) { types["Foo"].virtual_type }
      class Foo
      end

      class Bar < Foo
      end

      class Baz < Foo
      end

      a = Foo.new || Bar.new || Baz.new
      CRYSTAL
  end

  it "types method call of virtual type" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        def foo
          1
        end
      end

      class Bar < Foo
      end

      a = Foo.new || Bar.new
      a.foo
      CRYSTAL
  end

  it "types method call of virtual type with override" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64) }
      class Foo
        def foo
          1
        end
      end

      class Bar < Foo
        def foo
          1.5
        end
      end

      a = Foo.new || Bar.new
      a.foo
      CRYSTAL
  end

  it "dispatches virtual method" do
    nodes = parse(<<-CRYSTAL)
      class Foo
        def foo
        end
      end

      class Bar < Foo
      end

      class Baz < Foo
      end

      x = Foo.new || Bar.new || Baz.new
      x.foo
      CRYSTAL
    result = semantic nodes
    _, nodes = result.program, result.node.as(Expressions)
    nodes.last.as(Call).target_defs.not_nil!.size.should eq(1)
  end

  it "dispatches virtual method with overload" do
    nodes = parse(<<-CRYSTAL)
      class Foo
        def foo
        end
      end

      class Bar < Foo
        def foo
        end
      end

      class Baz < Foo
      end

      x = Foo.new || Bar.new || Baz.new
      x.foo
      CRYSTAL
    result = semantic nodes
    _, nodes = result.program, result.node.as(Expressions)
    nodes.last.as(Call).target_defs.not_nil!.size.should eq(2)
  end

  it "works with restriction alpha" do
    assert_no_errors <<-CRYSTAL
      require "prelude"

      class Foo
      end

      class Bar < Foo
        def foo
        end
      end

      class Baz < Bar
      end

      class Ban < Bar
      end

      a = [nil, Foo.new, Bar.new, Baz.new]
      a.push(Baz.new || Ban.new)
      CRYSTAL
  end

  it "doesn't check cover for subclasses" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64) }
      class Foo
        def foo(other)
          1
        end
      end

      class Bar < Foo
        def foo(other : Bar)
          1.5
        end
      end

      f = Foo.new || Bar.new
      x = f.foo(f)
      CRYSTAL
  end

  it "removes instance var from subclasses" do
    nodes = parse <<-CRYSTAL
      class Base
      end

      class Var < Base
        def x=(x : Int32)
          @x = x
        end
      end

      class Base
        def x=(x : Int32)
          @x = x
        end
      end

      v = Var.new
      v.x = 1
      v
      CRYSTAL
    result = semantic nodes
    mod = result.program

    var = mod.types["Var"]
    var.instance_vars.size.should eq(0)

    base = mod.types["Base"]
    base.instance_vars["@x"].type.should eq(mod.nilable(mod.int32))
  end

  it "types inspect" do
    assert_type(<<-CRYSTAL) { string }
      require "prelude"

      class Foo
      end

      Foo.new.inspect
      CRYSTAL
  end

  it "reports no matches for virtual type" do
    assert_error <<-CRYSTAL, "undefined method 'foo' for Foo"
      class Foo
      end

      class Bar < Foo
        def foo
        end
      end

      x = Foo.new || Bar.new
      x.foo
      CRYSTAL
  end

  it "doesn't check methods on abstract classes" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64) }
      abstract class Foo
      end

      class Bar1 < Foo
        def foo
          1
        end
      end

      class Bar2 < Foo
        def foo
          2.5
        end
      end

      f = Bar1.new || Bar2.new
      x = f.foo
      CRYSTAL
  end

  it "doesn't check methods on abstract classes 2" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64, char) }
      abstract class Foo
      end

      abstract class Bar < Foo
      end

      class Bar2 < Bar
        def foo
          1
        end
      end

      class Bar3 < Foo
        def foo
          2.5
        end
      end

      class Baz < Foo
        def foo
          'a'
        end
      end

      f = Bar2.new || Bar3.new || Baz.new
      x = f.foo
      CRYSTAL
  end

  it "reports undefined method in subclass of abstract class" do
    assert_error <<-CRYSTAL, "undefined method 'foo' for Bar3"
      abstract class Foo
      end

      abstract class Bar < Foo
      end

      class Bar2 < Bar
        def foo
          1
        end
      end

      class Bar3 < Bar
      end

      class Baz < Foo
        def foo
          'a'
        end
      end

      f = Bar2.new || Bar3.new || Baz.new
      x = f.foo
      CRYSTAL
  end

  it "doesn't check cover for abstract classes" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64, char) }
      abstract class Foo
        def foo(other)
          1
        end
      end

      abstract class Bar < Foo
      end

      class Bar1 < Bar
      end

      class Bar2 < Bar
      end

      class Baz < Foo
      end

      def foo(other : Bar1)
        1
      end

      def foo(other : Bar2)
        2.5
      end

      def foo(other : Baz)
        'a'
      end

      f = Bar1.new || Bar2.new || Baz.new
      foo(f)
      CRYSTAL
  end

  it "reports missing cover for subclass of abstract class" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be Bar1 or Baz, not Foo"
      abstract class Foo
        def foo(other)
          1
        end
      end

      abstract class Bar < Foo
      end

      class Bar1 < Bar
      end

      class Bar2 < Bar
      end

      class Baz < Foo
      end

      def foo(other : Bar1)
        1
      end

      def foo(other : Baz)
        'a'
      end

      f = Bar1.new || Bar2.new || Baz.new
      foo(f)
      CRYSTAL
  end

  it "checks cover in every concrete subclass" do
    assert_type(<<-CRYSTAL) { nil_type }
      abstract class Foo
      end

      abstract class Bar < Foo
      end

      class Bar1 < Bar
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      class Bar2 < Bar
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      class Baz < Foo
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      f = Bar1.new || Bar2.new || Baz.new
      f.foo(f)
      CRYSTAL
  end

  it "checks cover in every concrete subclass 2" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'Baz#foo' to be Bar1 or Baz, not Foo"
      abstract class Foo
      end

      abstract class Bar < Foo
      end

      class Bar1 < Bar
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      class Bar2 < Bar
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      class Baz < Foo
        def foo(x : Bar1); end
        def foo(x : Baz); end
      end

      f = Bar1.new || Bar2.new || Baz.new
      f.foo(f)
      CRYSTAL
  end

  it "checks cover in every concrete subclass 3" do
    assert_type(<<-CRYSTAL) { nil_type }
      abstract class Foo
      end

      abstract class Bar < Foo
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      class Bar1 < Bar
      end

      class Bar2 < Bar
      end

      class Baz < Foo
        def foo(x : Bar1); end
        def foo(x : Bar2); end
        def foo(x : Baz); end
      end

      f = Bar1.new || Bar2.new || Baz.new
      f.foo(f)
      CRYSTAL
  end

  it "checks method in every concrete subclass but method in Object" do
    assert_type(<<-CRYSTAL) { nil_type }
      class Object
        def foo
        end
      end

      abstract class Foo
      end

      class Bar1 < Foo
      end

      class Bar2 < Foo
      end

      f = Bar1.new || Bar2.new
      f.foo
      CRYSTAL
  end

  # it "recalculates virtual type when subclass is added" do
  #   assert_type(<<-CRYSTAL) { union_of(nil_type, int32, char) }
  #     class Foo
  #       def foo
  #         nil
  #       end
  #     end

  #     class Bar(T) < Foo
  #       def initialize(x : T)
  #         @x = x
  #       end

  #       def foo
  #         @x
  #       end
  #     end

  #     def coco(x)
  #       x.foo
  #     end

  #     a = Foo.new || Bar.new(1)
  #     b = coco(a)

  #     a2 = Foo.new || Bar.new('a')
  #     b2 = coco(a2)
  #     CRYSTAL
  # end

  it "finds overloads of union of virtual, class and nil" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64) }
      class Foo
      end

      class Bar < Foo
      end

      def foo(x : Reference)
        1
      end

      def foo(x : Value)
        1.5
      end

      f = Foo.new || Bar.new || Reference.new || nil
      foo(f)
      CRYSTAL
  end

  it "finds overloads of union of virtual, class and nil with abstract class" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64) }
      abstract class Foo
      end

      class Bar < Foo
      end

      class Baz < Foo
      end

      def foo(x : Reference)
        1
      end

      def foo(x : Value)
        1.5
      end

      f = Bar.new || Baz.new || Reference.new || nil
      foo(f)
      CRYSTAL
  end

  it "restricts with union and doesn't merge to super type" do
    assert_type(<<-CRYSTAL) { union_of(int32, char, string) }
      abstract class Foo
      end

      class Bar < Foo
        def foo
          1
        end
      end

      class Baz < Foo
        def foo
          'a'
        end
      end

      class Bag < Foo
      end

      def foo(x : Bar | Baz)
        x.foo
      end

      def foo(x)
        "hello"
      end

      f = Bar.new || Baz.new || Bag.new
      foo(f)
      CRYSTAL
  end

  it "uses virtual type as generic type if class is abstract" do
    assert_type(<<-CRYSTAL) { generic_class "Bar", types["Foo"].virtual_type }
      abstract class Foo
      end

      class Bar(T)
      end

      Bar(Foo).new
      CRYSTAL
  end

  it "uses virtual type as generic type if class is abstract even in union" do
    assert_type(<<-CRYSTAL) { generic_class "Bar", union_of(types["Foo"].virtual_type, int32) }
      abstract class Foo
      end

      class Baz < Foo
      end

      class Bar(T)
      end

      Bar(Foo | Int32).new
      CRYSTAL
  end

  it "automatically does virtual for generic type if there are subclasses" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { pointer_of(types["Foo"].virtual_type) }
      class Foo; end
      class Bar < Foo; end

      Pointer(Foo).malloc(1_u64)
      CRYSTAL
  end

  it "types instance var as virtual when using type declaration and has subclasses" do
    assert_type(<<-CRYSTAL) { types["Foo"].virtual_type! }
      class Foo
      end

      class Bar < Foo
        @foo : Foo

        def initialize
          @foo = Foo.new
        end

        def foo
          @foo
        end
      end

      Bar.new.foo
      CRYSTAL
  end
end
