require "../../spec_helper"

describe "Semantic: while" do
  it "types while" do
    assert_type("while 1; 1; end") { nil_type }
  end

  it "types while with break without value" do
    assert_type("while 1; break; end") { nil_type }
  end

  it "types while with break with value" do
    assert_type("while 1; break 'a'; end") { nilable char }
  end

  it "types while with multiple breaks with value" do
    assert_type(<<-CRYSTAL) { nilable union_of(char, tuple_of([string, int32])) }
      while 1
        break 'a' if 1
        break "", 123 if 1
      end
      CRYSTAL
  end

  it "types endless while with break without value" do
    assert_type("while true; break; end") { nil_type }
  end

  it "types endless while with break with value" do
    assert_type("while true; break 1; end") { int32 }
  end

  it "types endless while with multiple breaks with value" do
    assert_type(<<-CRYSTAL) { union_of(char, tuple_of([string, int32])) }
      while true
        break 'a' if 1
        break "", 123 if 1
      end
      CRYSTAL
  end

  it "reports break cannot be used outside a while" do
    assert_error "break",
      "invalid break"
  end

  it "types while true as NoReturn" do
    assert_type("while true; end") { no_return }
  end

  it "types while (true) as NoReturn" do
    assert_type("while (true); end") { no_return }
  end

  it "types while ((true)) as NoReturn" do
    assert_type("while ((true)); end") { no_return }
  end

  it "reports next cannot be used outside a while" do
    assert_error "next",
      "invalid next"
  end

  it "uses var type inside while if endless loop" do
    assert_type(<<-CRYSTAL) { int32 }
      a = nil
      while true
        a = 1
        break
      end
      a
      CRYSTAL
  end

  it "uses var type inside while if endless loop (2)" do
    assert_type(<<-CRYSTAL) { int32 }
      while true
        a = 1
        break
      end
      a
      CRYSTAL
  end

  it "marks variable as nil if breaking before assigning to it in an endless loop" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      a = nil
      while true
        break if 1 == 2
        a = 1
      end
      a
      CRYSTAL
  end

  it "marks variable as nil if breaking before assigning to it in an endless loop (2)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      while true
        break if 1 == 2
        a = 1
      end
      a
      CRYSTAL
  end

  it "types while with && (#1425)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      a = 1
      while a.is_a?(Int32) && (1 == 1)
        a = nil
      end
      a
      CRYSTAL
  end

  it "types while with assignment" do
    assert_type(<<-CRYSTAL) { int32 }
      while a = 1
        break
      end
      a
      CRYSTAL
  end

  it "types while with assignment and &&" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      while (a = 1) && (1 == 1)
        break
      end
      a
      CRYSTAL
  end

  it "types while with assignment and call" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      while (a = 1) > 0
        break
      end
      a
      CRYSTAL
  end

  it "doesn't modify var's type before while" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of(int32, char) }
      x = 'x'
      x.ord
      while 1 == 2
        x = 1
      end
      x
      CRYSTAL
  end

  it "restricts type after while (#4242)" do
    assert_type(<<-CRYSTAL) { int32 }
      a = nil
      while a.nil?
        a = 1
      end
      a
      CRYSTAL
  end

  it "restricts type after while with not (#4242)" do
    assert_type(<<-CRYSTAL) { int32 }
      a = nil
      while !a
        a = 1
      end
      a
      CRYSTAL
  end

  it "restricts type after `while` with `not` and `and` (#4242)" do
    assert_type(<<-CRYSTAL) { tuple_of [int32, char] }
      a = nil
      b = nil
      while !(a && b)
        a = 1
        b = 'a'
      end
      {a, b}
      CRYSTAL
  end

  it "doesn't restrict type after while if there's a break (#4242)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      a = nil
      while a.nil?
        if 1 == 1
          break
        end
        a = 1
      end
      a
      CRYSTAL
  end

  it "doesn't use type at end of endless while if variable is reassigned" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      while true
        a = 1
        if 1 == 1
          break
        end
        a = 'x'
      end
      a
      CRYSTAL
  end

  it "doesn't use type at end of endless while if variable is reassigned (2)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      a = ""
      while true
        a = 1
        if 1 == 1
          break
        end
        a = 'x'
      end
      a
      CRYSTAL
  end

  it "doesn't use type at end of endless while if variable is reassigned (3)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of(int32, char) }
      a = {1}
      while true
        a = a[0]
        if 1 == 1
          break
        end
        a = {'x'}
      end
      a
      CRYSTAL
  end

  it "uses type at end of endless while if variable is reassigned, but not before first break" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable union_of(int32, char) }
      while true
        if 1 == 1
          break
        end
        a = 1
        if 1 == 1
          break
        end
        a = 'x'
      end
      a
      CRYSTAL
  end

  it "uses type at end of endless while if variable is reassigned, but not before first break (2)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of(int32, char, string) }
      a = ""
      while true
        if 1 == 1
          break
        end
        a = 1
        if 1 == 1
          break
        end
        a = 'x'
      end
      a
      CRYSTAL
  end

  it "rebinds condition variable after while body (#6158)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable types["Foo"] }
      class Foo
        @parent : self?

        def parent
          @parent
        end
      end

      class Bar
        def initialize(@parent : Foo)
        end

        def parent
          @parent
        end
      end

      a = Foo.new
      b = Bar.new(a)
      while b = b.parent
        break if 1 == 1
      end
      b
      CRYSTAL
  end

  it "doesn't type var as nilable after break inside rescue" do
    assert_type(<<-CRYSTAL) { int32 }
      while true
        begin
          foo = 1
          break
        rescue
        end
      end
      foo
      CRYSTAL
  end

  it "types variable as nilable if raise before assign" do
    assert_type(<<-CRYSTAL) { nilable int32 }
      require "prelude"

      while true
        begin
          raise "oops"
          foo = 12345
        rescue
        end
        break
      end
      foo
      CRYSTAL
  end

  it "finds while cond assign target in Not (#10345)" do
    assert_type(<<-CRYSTAL) { int32 }
      while !(x = 1 || nil)
      end
      x
      CRYSTAL
  end

  it "keeps storage of var assigned only in short-circuited cond branch nilable (#16483)" do
    assert_type(<<-CRYSTAL) { nilable string }
      until true || (text = "hello")
      end
      text
      CRYSTAL
  end

  it "finds all while cond assign targets in expressions (#10350)" do
    assert_type(<<-CRYSTAL) { int32 }
      a = 1
      while ((b = 1); a)
        a = nil
        b = "hello"
      end
      b
      CRYSTAL
  end

  it "finds all while cond assign targets in expressions (2)" do
    assert_type(<<-CRYSTAL) { tuple_of [int32, int32] }
      def foo(x, y)
        true ? 1 : nil
      end

      while foo(a = 1, b = 1)
        a = nil
        b = "hello"
      end

      {a, b}
      CRYSTAL
  end

  it "finds all while cond assign targets in expressions (3)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable union_of(int32, char) }
      while 1 == 1 ? (x = 1; 1 == 1) : false
        x = 'a'
      end
      x
      CRYSTAL
  end

  it "finds all while cond assign targets in expressions (4)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of(int32, char, string) }
      x = ""
      while 1 == 1 ? (x = 1; 1 == 1) : false
        x = 'a'
      end
      x
      CRYSTAL
  end

  it "finds all while cond assign targets in expressions (5)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable union_of(int32, char) }
      while 1 == 1 ? (x = 1; 1 == 1) : false
        x
        x = 'a'
      end
      x
      CRYSTAL
  end

  it "finds all while cond assign targets in expressions (6)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { tuple_of [int32, int32] }
       while (x = true ? (y = 1) : 1; y = x; 1 == 1)
         x = 'a'
       end
       {x, y}
       CRYSTAL
  end

  it "doesn't fail on new variables inside typeof condition" do
    assert_type(<<-CRYSTAL) { nilable string }
      def foo
        while typeof(x = 1)
          return ""
        end
      end

      foo
      CRYSTAL
  end

  it "doesn't fail on nested conditionals inside typeof condition" do
    assert_type(<<-CRYSTAL) { nilable string }
      def foo
        while typeof(1 || 'a')
          return ""
        end
      end

      foo
      CRYSTAL
  end

  it "doesn't fail on Expressions condition (1)" do
    assert_type(<<-CRYSTAL) { union_of int32.metaclass, char }
      def foo
        while (v = 1; true)
          return typeof(v)
        end
        'a'
      end

      foo
      CRYSTAL
  end

  it "doesn't fail on Expressions condition (2)" do
    assert_type(<<-CRYSTAL) { union_of nil_type.metaclass, char }
      def foo
        while (v = nil; true)
          return typeof(v)
        end
        'a'
      end

      foo
      CRYSTAL
  end

  it "doesn't modify variables unchanged in condition and body" do
    assert_no_errors <<-CRYSTAL
      abstract class Base; end

      class A < Base; end

      class B < Base; end

      class C < Base; end

      def foo(x : A | B)
      end

      el = A.new.as(Base)
      if el.is_a?(A) || el.is_a?(B)
        while false
          break
        end

        foo(el)
      end
      CRYSTAL
  end
end
