require "../../spec_helper"

describe "Code gen: primitives" do
  it "codegens bool" do
    run("true").to_b.should be_true
    run("false").to_b.should be_false
  end

  it "codegens int" do
    run("1").to_i.should eq(1)
  end

  it "codegens long" do
    run("1_i64").to_i.should eq(1)
  end

  it "codegens int128" do
    # LLVM's JIT doesn't seem to support 128
    # bit integers well regarding GenericValue
    run(<<-CRYSTAL).to_i.should eq(1)
      require "prelude"

      1_i128.to_i
      CRYSTAL
  end

  it "codegens uint128" do
    # LLVM's JIT doesn't seem to support 128
    # bit integers well regarding GenericValue
    run(<<-CRYSTAL).to_i.should eq(1)
      require "prelude"

      1_u128.to_i
      CRYSTAL
  end

  it "codegens char" do
    run("'a'").to_i.should eq('a'.ord)
  end

  it "codegens char ord" do
    run("'a'.ord").to_i.should eq('a'.ord)
  end

  it "codegens f32" do
    run("2.5_f32").to_f32.should eq(2.5_f32)
  end

  it "codegens f64" do
    run("2.5_f64").to_f64.should eq(2.5_f64)
  end

  it "codegens string" do
    run(%("foo")).to_string.should eq("foo")
  end

  describe "arithmetic primitives" do
    # more detailed tests are done through the `primitives_spec` suite on a new
    # generation of the compiler

    it "codegens 1 + 2" do
      run(%(require "prelude"; 1 + 2)).to_i.should eq(3)
    end

    it "codegens 1 &+ 2" do
      run(%(1 &+ 2)).to_i.should eq(3)
    end

    it "codegens 1 - 2" do
      run(%(require "prelude"; 1 - 2)).to_i.should eq(-1)
    end

    it "codegens 1 &- 2" do
      run(%(1 &- 2)).to_i.should eq(-1)
    end

    it "codegens 2 * 3" do
      run(%(require "prelude"; 2 * 3)).to_i.should eq(6)
    end

    it "codegens 2 &* 3" do
      run(%(2 &* 3)).to_i.should eq(6)
    end

    it "codegens 8.unsafe_div 3" do
      run(%(8.unsafe_div 3)).to_i.should eq(2)
    end

    it "codegens 8.unsafe_mod 3" do
      run(%(10.unsafe_mod 3)).to_i.should eq(1)
    end

    it "codegens 16.unsafe_shr 2" do
      run(%(16.unsafe_shr 2)).to_i.should eq(4)
    end

    it "codegens 16.unsafe_shl 2" do
      run(%(16.unsafe_shl 2)).to_i.should eq(64)
    end

    it "codegens 1.to_i16!" do
      run("1.to_i16!").to_i.should eq(1)
    end

    it "codegens 1.to_i16" do
      run(%(require "prelude"; 1.to_i16)).to_i.should eq(1)
    end

    it "codegens 1.to_f!" do
      run("1.to_f!").to_f64.should eq(1.0)
    end

    it "codegens 1.to_f" do
      run(%(require "prelude"; 1.to_f)).to_f64.should eq(1.0)
    end

    it "skips bounds checking when to_i produces same type" do
      run("1.to_i32").to_i.should eq(1)
    end
  end

  it "defined method that calls primitive (bug)" do
    run(<<-CRYSTAL).to_i.should eq(1)
      struct Int64
        def foo
          to_u64!
        end
      end

      a = 1_i64
      a.foo.to_i!
      CRYSTAL
  end

  it "codegens __LINE__" do
    run(<<-CRYSTAL, inject_primitives: false).to_i.should eq(2)

      __LINE__
      CRYSTAL
  end

  it "codegens crystal_type_id with union type" do
    run(<<-CRYSTAL).to_b.should be_true
      class Foo
      end

      class Bar < Foo
      end

      f = Foo.allocate || Bar.allocate
      f.crystal_type_id == Foo.allocate.crystal_type_id
      CRYSTAL
  end

  it "doesn't treat `(1 == 1) == true` as `1 == 1 == true` (#328)" do
    run("(1 == 1) == true").to_b.should be_true
  end

  it "passes issue #328" do
    run("((1 == 1) != (2 == 2))").to_b.should be_false
  end

  pending "codegens pointer of int" do
    run(<<-CRYSTAL).to_i.should eq(5)
      ptr = Pointer(Int).malloc(1_u64)
      ptr.value = 1
      ptr.value = 2_u8
      ptr.value = 3_u16
      ptr.value = 4_u32
      (ptr.value + 1).to_i32
      CRYSTAL
  end

  pending "sums two numbers out of an [] of Number" do
    run(<<-CRYSTAL).to_f32.should eq(2.5)
      p = Pointer(Number).malloc(2_u64)
      p.value = 1
      (p + 1_i64).value = 1.5

      (p.value + (p + 1_i64).value).to_f32
      CRYSTAL
  end

  it "codegens crystal_type_id for class" do
    codegen(%(String.crystal_type_id))
  end

  it "can invoke cast on primitive typedef (#614)" do
    codegen(<<-CRYSTAL)
      lib Test
        type K = Int32
        fun foo : K
      end

      Test.foo.to_i!
      CRYSTAL
  end

  it "can invoke binary on primitive typedef (#614)" do
    codegen(<<-CRYSTAL)
      lib Test
        type K = Int32
        fun foo : K
      end

      Test.foo &+ 1
      CRYSTAL
  end

  it "can invoke binary on primitive typedef (2) (#16097)" do
    codegen(<<-CRYSTAL)
      lib Test
        type K = Int32
        fun foo : K
      end

      Test.foo == Test.foo
      CRYSTAL
  end

  it "can invoke pointer primitives on typedef" do
    codegen(<<-CRYSTAL)
      lib Test
        type K = Void*
        fun foo : K
      end

      Test.foo + 1
      Test.foo - Test.foo
      Test.foo.realloc(1)
      CRYSTAL
  end

  it "can invoke struct setter on primitive typedef" do
    codegen(<<-CRYSTAL)
      lib Test
        struct Foo
          x : Int32
        end

        type K = Foo
        fun foo : K
      end

      Test.foo.x = 1
      CRYSTAL
  end

  pending "can invoke proc call on primitive typedef" do
    codegen(<<-CRYSTAL)
      lib Test
        type K = ->
        fun foo : K
      end

      Test.foo.call
      CRYSTAL
  end

  it "allows redefining a primitive method" do
    run(<<-CRYSTAL).to_i.should eq(42)
      struct Int32
        def *(other : Int32)
          42
        end
      end

      1 * 2
      CRYSTAL
  end

  it "doesn't optimize away call whose obj is not passed as self (#2226)" do
    run(<<-CRYSTAL).to_i.should eq(2)
      class Global
        @@x = 0

        def self.x=(@@x)
        end

        def self.x
          @@x
        end
      end

      def foo
        Global.x = 2
        3
      end

      foo.class.crystal_type_id

      Global.x
      CRYSTAL
  end

  it "uses built-in llvm function that returns a tuple" do
    run(<<-CRYSTAL).to_i.should eq(3)
      lib Intrinsics
        fun sadd_i32_with_overflow = "llvm.sadd.with.overflow.i32"(a : Int32, b : Int32) : {Int32, Bool}
      end

      x, o = Intrinsics.sadd_i32_with_overflow(1, 2)
      x
      CRYSTAL
  end

  it "gets crystal class instance type id" do
    run(<<-CRYSTAL).to_b.should be_true
      class Foo
      end

      Foo.new.crystal_type_id == Foo.crystal_instance_type_id
      CRYSTAL
  end

  describe "va_arg" do
    # On Windows and AArch64 llvm's va_arg instruction works incorrectly.
    {% unless flag?(:win32) || flag?(:aarch64) %}
      it "uses llvm's va_arg instruction" do
        mod = codegen(<<-CRYSTAL)
          struct VaList
            @[Primitive(:va_arg)]
            def next(type)
            end
          end

          list = VaList.new
          list.next(Int32)
          CRYSTAL
        type = {% if LibLLVM::IS_LT_150 %} "%VaList*" {% else %} "ptr" {% end %}
        str = mod.to_s
        str.should contain("va_arg #{type} %list")
      end

      it "works with C code" do
        test_c(<<-C, <<-CRYSTAL, &.to_i.should eq(6))
          extern int foo_f(int,...);
          int foo() {
            return foo_f(3,1,2,3);
          }
          C
          lib LibFoo
            fun foo() : LibC::Int
          end

          fun foo_f(count : Int32, ...) : LibC::Int
            sum = 0
            VaList.open do |list|
              count.times do |i|
                sum += list.next(Int32)
              end
            end
            sum
          end

          LibFoo.foo
          CRYSTAL
      end
    {% end %}
  end

  describe "atomicrmw" do
    it "codegens atomicrmw with enums" do
      run(<<-CRYSTAL).to_i.should eq(3)
        enum RMWBinOp
          Add = 1
        end

        enum Ordering
          SequentiallyConsistent = 7
        end

        @[Primitive(:atomicrmw)]
        def atomicrmw(op : RMWBinOp, ptr : Int32*, val : Int32, ordering : Ordering, singlethread : Bool) : Int32
        end

        x = 1
        atomicrmw(:add, pointerof(x), 2, :sequentially_consistent, false)
        x
        CRYSTAL
    end

    it "codegens atomicrmw with enums" do
      run(<<-CRYSTAL).to_i.should eq(3)
        enum RMWBinOp
          Add = 1
        end

        enum Ordering
          SequentiallyConsistent = 7
        end

        @[Primitive(:atomicrmw)]
        def atomicrmw(op : RMWBinOp, ptr : Int32*, val : Int32, ordering : Ordering, singlethread : Bool) : Int32
        end

        x = 1
        atomicrmw(RMWBinOp::Add, pointerof(x), 2, Ordering::SequentiallyConsistent, false)
        x
        CRYSTAL
    end

    # TODO: remove once support for 1.4 is dropped
    it "codegens atomicrmw with symbols" do
      run(<<-CRYSTAL).to_i.should eq(3)
        @[Primitive(:atomicrmw)]
        def atomicrmw(op : Symbol, ptr : Int32*, val : Int32, ordering : Symbol, singlethread : Bool) : Int32
        end

        x = 1
        atomicrmw(:add, pointerof(x), 2, :sequentially_consistent, false)
        x
        CRYSTAL
    end
  end

  it "allows @[Primitive] on method that has body" do
    run(<<-CRYSTAL).to_string.should eq("hello")
      module Moo
        @[Primitive(:symbol_to_s)]
        def self.symbol_to_s(symbol : Symbol) : String
          untyped
        end
      end

      Moo.symbol_to_s(:hello)
      CRYSTAL
  end

  it "allows @[Primitive] on fun declarations" do
    run(<<-CRYSTAL).to_i.should eq(1)
      lib LibFoo
        @[Primitive(:enum_value)]
        fun enum_value(x : Int32) : Int32
      end

      LibFoo.enum_value(1)
      CRYSTAL
  end
end
