require "../../spec_helper"

describe "Codegen: special vars" do
  ["$~", "$?"].each do |name|
    it "codegens #{name}" do
      run(<<-CRYSTAL).to_string.should eq("hey")
        class Object; def not_nil!; self; end; end

        def foo(z)
          #{name} = "hey"
        end

        foo(2)
        #{name}
        CRYSTAL
    end

    it "codegens #{name} with nilable (1)" do
      run(<<-CRYSTAL).to_string.should eq("ouch")
        require "prelude"

        def foo
          if 1 == 2
            #{name} = "foo"
          end
        end

        foo

        begin
          #{name}
        rescue ex
          "ouch"
        end
        CRYSTAL
    end

    it "codegens #{name} with nilable (2)" do
      run(<<-CRYSTAL).to_string.should eq("foo")
        require "prelude"

        def foo
          if 1 == 1
            #{name} = "foo"
          end
        end

        foo

        begin
          #{name}
        rescue ex
          "ouch"
        end
        CRYSTAL
    end
  end

  it "codegens $~ two levels" do
    run(<<-CRYSTAL).to_string.should eq("hey")
      class Object; def not_nil!; self; end; end

      def foo
        $? = "hey"
      end

      def bar
        $? = foo
        $?
      end

      bar
      $?
      CRYSTAL
  end

  it "works lazily" do
    run(<<-CRYSTAL).to_string.should eq("bar")
      require "prelude"

      class Foo
        getter string

        def initialize(@string : String)
        end
      end

      def bar(&block : Foo -> _)
        block
      end

      block = bar do |foo|
        case foo.string
        when /foo-(.+)/
          $1
        else
          "baz"
        end
      end
      block.call(Foo.new("foo-bar"))
      CRYSTAL
  end

  it "codegens in block" do
    run(<<-CRYSTAL).to_string.should eq("hey")
      require "prelude"

      class Object; def not_nil!; self; end; end

      def foo
        $~ = "hey"
        yield
      end

      a = nil
      foo do
        a = $~
      end
      a.not_nil!
      CRYSTAL
  end

  it "codegens in block with nested block" do
    run(<<-CRYSTAL).to_string.should eq("hey")
      require "prelude"

      class Object; def not_nil!; self; end; end

      def bar
        yield
      end

      def foo
        bar do
          $~ = "hey"
          yield
        end
      end

      a = nil
      foo do
        a = $~
      end
      a.not_nil!
      CRYSTAL
  end

  it "codegens after block" do
    run(<<-CRYSTAL).to_string.should eq("hey")
      require "prelude"

      class Object; def not_nil!; self; end; end

      def foo
        $~ = "hey"
        yield
      end

      a = nil
      foo {}
      $~
      CRYSTAL
  end

  it "codegens after block 2" do
    run(<<-CRYSTAL).to_string.should eq("bye")
      class Object; def not_nil!; self; end; end

      def baz
        $~ = "bye"
      end

      def foo
        baz
        yield
        $~
      end

      foo do
      end
      CRYSTAL
  end

  it "codegens with default argument" do
    run(<<-CRYSTAL).to_string.should eq("bye")
      class Object; def not_nil!; self; end; end

      def baz(x = 1)
        $~ = "bye"
      end

      baz
      $~
      CRYSTAL
  end

  it "preserves special vars in macro expansion with call with default arguments (#824)" do
    run(<<-CRYSTAL).to_string.should eq("yes")
      class Object; def not_nil!; self; end; end

      def bar(x = 0)
        $~ = "yes"
      end

      macro foo
        bar
        $~
      end

      foo
      CRYSTAL
  end

  it "allows with primitive" do
    run(<<-CRYSTAL).to_i.should eq(123)
      class Object; def not_nil!; self; end; end

      def foo
        $~ = 123
      end

      foo

      v = $~
      v || 456
      CRYSTAL
  end

  it "allows with struct" do
    run(<<-CRYSTAL).to_i.should eq(123)
      class Object; def not_nil!; self; end; end

      struct Foo
        def initialize(@x : Int32)
        end

        def x
          @x
        end
      end

      def foo
        $~ = Foo.new(123)
      end

      foo

      v = $~
      if v
        v.x
      else
        456
      end
      CRYSTAL
  end

  it "preserves special vars if initialized inside block (#2194)" do
    run(<<-CRYSTAL).to_string.should eq("foo")
      class Object; def not_nil!; self; end; end

      def foo
        $~ = "foo"
      end

      def bar
        yield
      end

      bar do
        foo
      end

      v = $~
      if v
        v
      else
        "bar"
      end
      CRYSTAL
  end
end
