require "../../spec_helper"

describe "Semantic: extern struct" do
  it "declares extern struct with no constructor" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Extern]
      struct Foo
        @x = uninitialized Int32

        def x
          @x
        end
      end

      Foo.new.x
      CRYSTAL
  end

  it "declares with constructor" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Extern]
      struct Foo
        @x = uninitialized Int32

        def initialize(@x)
        end

        def foo
          @x
        end
      end

      Foo.new(1).foo
      CRYSTAL
  end

  it "overrides getter" do
    assert_type(<<-CRYSTAL) { char }
      @[Extern]
      struct Foo
        @x = uninitialized Int32

        def x
          'a'
        end
      end

      Foo.new.x
      CRYSTAL
  end

  it "can be passed to C fun" do
    assert_type(<<-CRYSTAL) { float64 }
      @[Extern]
      struct Foo
        @x = uninitialized Int32
      end

      lib LibFoo
        fun foo(x : Foo) : Float64
      end

      LibFoo.foo(Foo.new)
      CRYSTAL
  end

  it "can include module" do
    assert_type(<<-CRYSTAL) { int32 }
      module Moo
        @x = uninitialized Int32

        def x
          @x
        end
      end

      @[Extern]
      struct Foo
        include Moo
      end

      Foo.new.x
      CRYSTAL
  end

  it "errors if using non-primitive for field type" do
    assert_error <<-CRYSTAL, "only primitive types, pointers, structs, unions, enums and tuples are allowed in extern struct declarations"
      class Bar
      end

      @[Extern]
      struct Foo
        @x = uninitialized Bar
      end
      CRYSTAL
  end

  it "errors if using non-primitive for field type via module" do
    assert_error <<-CRYSTAL, "only primitive types, pointers, structs, unions, enums and tuples are allowed in extern struct declarations"
      class Bar
      end

      module Moo
        @x = uninitialized Bar
      end

      @[Extern]
      struct Foo
        include Moo
      end
      CRYSTAL
  end

  it "errors if using non-primitive type in constructor" do
    assert_error <<-CRYSTAL, "only primitive types, pointers, structs, unions, enums and tuples are allowed in extern struct declarations"
      class Bar
      end

      @[Extern]
      struct Foo
        def initialize
          @x = Bar.new
        end
      end
      CRYSTAL
  end

  it "declares extern union with no constructor" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Extern(union: true)]
      struct Foo
        @x = uninitialized Int32

        def x
          @x
        end
      end

      Foo.new.x
      CRYSTAL
  end

  it "can use extern struct in lib" do
    assert_type(<<-CRYSTAL) { types["Foo"] }
      @[Extern]
      struct Foo
      end

      lib LibFoo
        fun foo(x : Foo) : Foo
      end

      foo = Foo.new
      LibFoo.foo(foo)
      CRYSTAL
  end

  it "can new with named args" do
    assert_type(<<-CRYSTAL) { types["A"] }
      @[Extern]
      struct A
        def initialize(@x : Int32)
        end
      end

      A.new(x: 6)
      CRYSTAL
  end
end
