require "../../spec_helper"

describe "Codegen: class var" do
  it "codegens class var" do
    run(<<-CRYSTAL).to_i.should eq(1)
      class Foo
        @@foo = 1

        def self.foo
          @@foo
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "codegens class var as nil" do
    run(<<-CRYSTAL).to_i.should eq(0)
      struct Nil; def to_i; 0; end; end

      class Foo
        @@foo = nil

        def self.foo
          @@foo
        end
      end

      Foo.foo.to_i
      CRYSTAL
  end

  it "codegens class var inside instance method" do
    run(<<-CRYSTAL).to_i.should eq(1)
      class Foo
        @@foo = 1

        def foo
          @@foo
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "codegens class var as nil if assigned for the first time inside method" do
    run(<<-CRYSTAL).to_i.should eq(1)
      struct Nil; def to_i!; 0; end; end

      class Foo
        def self.foo
          @@foo = 1
          @@foo
        end
      end

      Foo.foo.to_i!
      CRYSTAL
  end

  it "codegens class var inside module" do
    run(<<-CRYSTAL).to_i.should eq(1)
      module Foo
        @@foo = 1

        def self.foo
          @@foo
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "accesses class var from proc literal" do
    run(<<-CRYSTAL).to_i.should eq(1)
      class Foo
        @@a = 1

        def self.foo
          ->{ @@a }.call
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "reads class var before initializing it (hoisting)" do
    run(<<-CRYSTAL).to_i.should eq(42)
      x = Foo.var

      class Foo
        @@var = 42

        def self.var
          @@var
        end
      end

      x
      CRYSTAL
  end

  it "uses var in class var initializer" do
    run(<<-CRYSTAL).to_i.should eq(6)
      class Foo
        @@var : Int32
        @@var = begin
          a = class_method
          a &+ 3
        end

        def self.var
          @@var
        end

        def self.class_method
          1 &+ 2
        end
      end

      Foo.var
      CRYSTAL
  end

  it "reads simple class var before another complex one" do
    run(<<-CRYSTAL).to_i.should eq(42)
      class Foo
        @@var2 : Int32
        @@var2 = @@var &+ 1

        @@var = 41

        def self.var2
          @@var2
        end
      end

      Foo.var2
      CRYSTAL
  end

  it "initializes class var of union with single type" do
    run(<<-CRYSTAL).to_i.should eq(42)
      class Foo
        @@var : Int32 | String
        @@var = 42

        def self.var
          @@var
        end
      end

      var = Foo.var
      if var.is_a?(Int32)
        var
      else
        0
      end
      CRYSTAL
  end

  it "initializes class var with array literal" do
    run(<<-CRYSTAL).to_i.should eq(3)
      require "prelude"

      class Foo
        @@var = [1, 2, 4]

        def self.var
          @@var
        end
      end

      Foo.var.size
      CRYSTAL
  end

  it "codegens second class var initializer" do
    run(<<-CRYSTAL).to_i.should eq(2)
      class Foo
        @@var = 1
        @@var = 2

        def self.var
          @@var
        end
      end

      Foo.var
      CRYSTAL
  end

  it "initializes dependent constant before class var" do
    run(<<-CRYSTAL).to_i.should eq(3)
      require "prelude"

      def foo
        a = 1
        b = 2
        a &+ b
      end

      CONST = foo()

      class Foo
        @@foo : Int32
        @@foo = CONST

        def self.foo
          @@foo
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "declares and initializes" do
    run(<<-CRYSTAL).to_i.should eq(42)
      class Foo
        @@x : Int32 = 42

        def self.x
          @@x
        end
      end

      Foo.x
      CRYSTAL
  end

  it "doesn't use nilable type for initializer" do
    run(<<-CRYSTAL).to_i.should eq(42)
      class Foo
        @@foo : Int32?
        @@foo = 42

        @@bar : Int32?
        @@bar = @@foo

        def self.bar
          @@bar
        end
      end

      Foo.bar || 10
      CRYSTAL
  end

  it "codegens class var with begin and vars" do
    run(<<-CRYSTAL).to_i.should eq(3)
      class Foo
        @@foo : Int32
        @@foo = begin
          a = 1
          b = 2
          a &+ b
        end

        def self.foo
          @@foo
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "codegens class var with type declaration begin and vars" do
    run(<<-CRYSTAL).to_i.should eq(3)
      class Foo
        @@foo : Int32 = begin
          a = 1
          b = 2
          a &+ b
        end

        def self.foo
          @@foo
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "codegens class var with nilable reference type" do
    run(<<-CRYSTAL).to_string.should eq("hello")
      class Foo
        @@foo : String? = nil

        def self.foo
          @@foo ||= "hello"
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "initializes class var the moment it reaches it" do
    run(<<-CRYSTAL).to_string.should eq("BAR")
      require "prelude"

      ENV["FOO"] = "BAR"

      class Foo
        @@x = ENV["FOO"]

        def self.x
          @@x
        end
      end

      w = Foo.x
      z = Foo.x
      z
      CRYSTAL
  end

  it "gets pointerof class var" do
    run(<<-CRYSTAL).to_i.should eq(10)
      z = Foo.foo

      class Foo
        @@foo = 10

        def self.foo
          pointerof(@@foo).value
        end
      end

      z
      CRYSTAL
  end

  it "gets pointerof class var complex constant" do
    run(<<-CRYSTAL).to_i.should eq(10)
      z = Foo.foo

      class Foo
        @@foo : Int32
        @@foo = begin
          a = 10
          a
        end

        def self.foo
          pointerof(@@foo).value
        end
      end

      z
      CRYSTAL
  end

  it "doesn't inherit class var value in subclass" do
    run(<<-CRYSTAL).to_i.should eq(1)
      class Foo
        @@var = 1

        def self.var
          @@var
        end

        def self.var=(@@var)
        end
      end

      class Bar < Foo
      end

      Foo.var = 2

      Bar.var
      CRYSTAL
  end

  it "doesn't inherit class var value in module" do
    run(<<-CRYSTAL).to_i.should eq(1)
      module Moo
        @@var = 1

        def var
          @@var
        end

        def self.var=(@@var)
        end
      end

      class Foo
        include Moo
      end

      Moo.var = 2

      Foo.new.var
      CRYSTAL
  end

  it "reads class var from virtual type" do
    run(<<-CRYSTAL).to_i.should eq(2)
      class Foo
        @@var = 1

        def self.var=(@@var)
        end

        def self.var
          @@var
        end

        def var
          @@var
        end
      end

      class Bar < Foo
      end

      Bar.var = 2

      ptr = Pointer(Foo).malloc(1_u64)
      ptr.value = Bar.new
      ptr.value.var
      CRYSTAL
  end

  it "reads class var from virtual type metaclass" do
    run(<<-CRYSTAL).to_i.should eq(2)
      class Foo
        @@var = 1

        def self.var=(@@var)
        end

        def self.var
          @@var
        end
      end

      class Bar < Foo
      end

      Bar.var = 2

      ptr = Pointer(Foo.class).malloc(1_u64)
      ptr.value = Bar
      ptr.value.var
      CRYSTAL
  end

  it "writes class var from virtual type" do
    run(<<-CRYSTAL).to_i.should eq(2)
      class Foo
        @@var = 1

        def self.var=(@@var)
        end

        def self.var
          @@var
        end

        def var=(@@var)
        end
      end

      class Bar < Foo
      end

      ptr = Pointer(Foo).malloc(1_u64)
      ptr.value = Bar.new
      ptr.value.var = 2

      Bar.var
      CRYSTAL
  end

  it "declares var as uninitialized and initializes it unsafely" do
    run(<<-CRYSTAL).to_i.should eq(10)
      class Foo
        @@x = uninitialized Int32
        @@x = Foo.bar

        def self.bar
          if 1 == 2
            @@x
          else
            10
          end
        end

        def self.x
          @@x
        end
      end

      Foo.x
      CRYSTAL
  end

  it "doesn't crash with pointerof from another module" do
    run(<<-CRYSTAL).to_i.should eq(1)
      require "prelude"

      class Foo
        @@x : Int32?
        @@x = 1

        def self.x
          pointerof(@@x).value
        end
      end

      class Bar
        def self.bar
          Foo.x
        end
      end

      Bar.bar
      CRYSTAL
  end

  it "codegens generic class with class var" do
    run(<<-CRYSTAL).to_i.should eq(1 + 1 + 10 + 20)
      class Foo(T)
        @@bar = 1

        def bar
          @@bar
        end

        def bar=(@@bar)
        end
      end

      f1 = Foo(Int32).new
      f2 = Foo(String).new

      a = f1.bar
      b = f2.bar
      f1.bar = 10
      c = f2.bar
      f2.bar = 20
      d = f1.bar
      a &+ b &+ c &+ d
      CRYSTAL
  end

  it "inline initialization of simple class var" do
    mod = codegen(<<-CRYSTAL)
      class Foo
        @@x = 1
      end
      CRYSTAL

    mod.to_s.should_not contain("x:init")
  end

  it "doesn't error if class var shares name with const (#7865)" do
    run(<<-CRYSTAL).to_string.should eq("asdfgh")
      require "prelude"

      class Pattern
        @@A = "asdf"
        A = "\#{@@A}gh"
      end

      Pattern::A
      CRYSTAL
  end

  it "catch infinite loop in class var initializer" do
    run(<<-CRYSTAL).to_string.should eq("error: Recursion while initializing class variables and/or constants")
      require "prelude"

      module Crystal
        def self.main_user_code(argc : Int32, argv : UInt8**)
          LibCrystalMain.__crystal_main(argc, argv)
        rescue ex
          print "error: \#{ex.message}"
        end
      end

      class Foo
        @@x : Int32 = init

        def self.init
          @@x + 1
        end

        def self.x
          @@x
        end
      end

      nil
      CRYSTAL
  end

  it "runs class var side effects (#8862)" do
    run(<<-CRYSTAL).to_i.should eq(6)
      require "prelude"

      class Foo
        @@x = 0

        def self.set
          @@x = 3
        end

        def self.x
          @@x
        end
      end

      a = Hello.value

      class Hello
        @@value : Int32 = begin
          Foo.set
          1 &+ 2
        end

        def self.value
          @@value
        end
      end

      a &+ Foo.x
      CRYSTAL
  end
end
