require "../../spec_helper"

describe "Semantic: if" do
  it "types an if without else" do
    assert_type("if 1 == 1; 1; end", inject_primitives: true) { nilable int32 }
  end

  it "types an if with else of same type" do
    assert_type("if 1 == 1; 1; else; 2; end", inject_primitives: true) { int32 }
  end

  it "types an if with else of different type" do
    assert_type("if 1 == 1; 1; else; 'a'; end", inject_primitives: true) { union_of(int32, char) }
  end

  it "types `if` with `&&` and assignment" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      struct Number
        def abs
          self
        end
      end

      class Foo
        def coco
          @a = 1 || nil
          if (b = @a) && 1 == 1
            b.abs
          end
        end
      end

      Foo.new.coco
      CRYSTAL
  end

  it "can invoke method on var that is declared on the right hand side of an and" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      if 1 == 2 && (b = 1)
        b + 1
      end
      CRYSTAL
  end

  it "errors if requires inside if" do
    assert_error <<-CRYSTAL, "can't require dynamically"
      if 1 == 2
        require "foo"
      end
      CRYSTAL
  end

  it "correctly filters type of variable if there's a raise with an interpolation that can't be typed" do
    assert_type(<<-CRYSTAL) { int32 }
      require "prelude"

      def bar
        bar
      end

      def foo
        a = 1 || nil
        unless a
          raise "Oh no \#{bar}"
        end
        a + 2
      end

      foo
      CRYSTAL
  end

  it "passes bug (related to #1729)" do
    assert_type(<<-CRYSTAL) { union_of(int32, float64) }
      n = true ? 3 : 3.2
      if n.is_a?(Float64)
        n
      end
      n
      CRYSTAL
  end

  it "restricts the type of the right hand side of an || when using is_a? (#1728)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { bool }
      n = 3 || "foobar"
      n.is_a?(String) || (n + 1 == 2)
      CRYSTAL
  end

  it "restricts type with !var and ||" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of bool, int32 }
      a = 1 == 1 ? 1 : nil
      !a || a + 2
      CRYSTAL
  end

  it "restricts type with !var.is_a?(...) and ||" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of bool, int32 }
      a = 1 == 1 ? 1 : nil
      !a.is_a?(Int32) || a + 2
      CRYSTAL
  end

  it "restricts type with !var.is_a?(...) and &&" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { union_of bool, int32 }
      a = 1 == 1 ? 1 : ""
      !a.is_a?(String) && a + 2
      CRYSTAL
  end

  it "restricts with || (#2464)" do
    assert_type(<<-CRYSTAL) { int32 }
      struct Int32
        def foo
          1
        end
      end

      struct Char
        def foo
          1
        end
      end

      a = 1 || "" || 'a'
      if a.is_a?(Int32) || a.is_a?(Char)
        a.foo
      else
        1
      end
      CRYSTAL
  end

  it "doesn't restrict with || on different vars" do
    assert_error <<-CRYSTAL, "undefined method"
      struct Int32
        def foo
          1
        end
      end

      struct Char
        def bar
          1
        end
      end

      a = 1 || "" || 'a'
      b = a
      if a.is_a?(Int32) || b.is_a?(Char)
        a.foo + b.bar
      end
      CRYSTAL
  end

  it "doesn't restrict with || on var and non-restricting condition" do
    assert_error <<-CRYSTAL, "undefined method"
      struct Int32
        def foo
          1
        end
      end

      a = 1 || "" || 'a'
      if a.is_a?(Int32) || 1 == 2
        a.foo
      end
      CRYSTAL
  end

  it "restricts with || but doesn't unify types to base class" do
    assert_type(<<-CRYSTAL) { union_of(nil_type, int32, char) }
      class Foo
      end

      class Bar < Foo
        def foo
          1
        end
      end

      class Baz < Foo
        def foo
          'a'
        end
      end

      a = Bar.new.as(Foo)
      if a.is_a?(Bar) || a.is_a?(Baz)
        a.foo
      else
        nil
      end
      CRYSTAL
  end

  it "restricts with && always falsey" do
    assert_type(<<-CRYSTAL) { union_of(bool, int32) }
      x = 1
      if (x.is_a?(String) && x.is_a?(String)) && x.is_a?(String)
        true
      else
        2
      end
      CRYSTAL
  end

  it "doesn't filter and recombine when variables don't change in if" do
    assert_type(<<-CRYSTAL) { int32 }
      module Moo
      end

      class Foo
      end

      class Bar < Foo
        include Moo
      end

      class Baz < Foo
        include Moo
      end

      def foo(x : Foo)
        1
      end

      x = Bar.new.as(Moo)
      if x.is_a?(Bar) || x.is_a?(Baz)
      end
      foo(x)
      CRYSTAL
  end

  it "restricts and doesn't unify union types" do
    assert_type(<<-CRYSTAL) { union_of(nil_type, int32) }
      class Foo
      end

      module M
        def m
          1
        end
      end

      class Bar < Foo
        include M
      end

      class Baz < Foo
        include M
      end

      a = Bar.new.as(Foo)
      if b = a.as?(M)
        b.m
      else
        nil
      end
      CRYSTAL
  end

  it "types variable after unreachable else of && (#3360)" do
    assert_type(<<-CRYSTAL) { int32 }
      def test
        foo = 1 if 1
        return 1 unless foo && foo
        foo
      end

      test
      CRYSTAL
  end

  it "restricts || else (1) (#3266)" do
    assert_type(<<-CRYSTAL) { tuple_of([int32, int32]) }
      a = 1 || nil
      b = 1 || nil
      if !a || !b
        {1, 2}
      else
        {a, b}
      end
      CRYSTAL
  end

  it "restricts || else (2) (#3266)" do
    assert_type(<<-CRYSTAL) { union_of char, int32 }
      a = 1 || nil
      if !a || 1
        'c'
      else
        a
      end
      CRYSTAL
  end

  it "restricts || else (3) (#3266)" do
    assert_type(<<-CRYSTAL) { union_of char, int32 }
      a = 1 || nil
      if 1 || !a
        'c'
      else
        a
      end
      CRYSTAL
  end

  it "doesn't restrict || else in sub && (right)" do
    assert_type(<<-CRYSTAL) { nilable int32 }
      def foo
        a = 1 || nil

        if false || (!a && false)
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "doesn't restrict || else in sub && (left)" do
    assert_type(<<-CRYSTAL) { nilable int32 }
      def foo
        a = 1 || nil

        if (!a && false) || false
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "restricts || else in sub || (right)" do
    assert_type(<<-CRYSTAL) { int32 }
      def foo
        a = 1 || nil

        if false || (!a || false)
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "restricts || else in sub || (left)" do
    assert_type(<<-CRYSTAL) { int32 }
      def foo
        a = 1 || nil

        if (!a || false) || false
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "restricts && else in sub && (right)" do
    assert_type(<<-CRYSTAL) { int32 }
      def foo
        a = 1 || nil

        if !a && (!a && !a)
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "restricts && else in sub && (left)" do
    assert_type(<<-CRYSTAL) { int32 }
      def foo
        a = 1 || nil

        if (!a && !a) && !a
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "restricts || of more than 2 clauses (#8864)" do
    assert_type(<<-CRYSTAL) { int32 }
      def foo
        a = 1 || 2.0 || 'c' || ""

        if a.is_a?(Float64) || a.is_a?(Char) || a.is_a?(String)
          return 1
        end

        a
      end

      foo
      CRYSTAL
  end

  it "restricts && of !var.is_a(...)" do
    assert_type(<<-CRYSTAL) { union_of int32, float64, bool }
      def foo
        a = 1 || 2.0 || 'c'

        if !a.is_a?(Int32) && !a.is_a?(Float64)
          return true
        end

        a
      end

      foo
      CRYSTAL
  end

  it "doesn't consider nil type in else branch with if with && (#7434)" do
    assert_type(<<-CRYSTAL) { int32 }
      def foo
        if true && (y = 1 || nil)
        else
          return 1
        end
        y
      end

      foo
      CRYSTAL
  end

  it "doesn't add Nil to var assigned in && condition with method call (#15739)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      def foo
        if true && (x = 1) == 1
          x
        else
          0
        end
      end
      foo
      CRYSTAL
  end

  it "types var assigned in && condition correctly when used after (#15739)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      def bar : Int32
        1
      end

      def foo
        if true && (x = bar) > 0
          x
        else
          0
        end
      end
      foo
      CRYSTAL
  end

  it "allows chained comparisons of 4+ expressions (#16361)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { bool }
      def a; 1; end
      def b; 2; end
      def c; 3; end
      def d; 4; end

      a < b < c < d
      CRYSTAL
  end

  it "includes pointer types in falsey branch" do
    assert_type(<<-CRYSTAL) { nilable union_of bool, pointer_of(int32), int32 }
      def foo
        x = 1
        y = false || pointerof(x) || nil

        if !y
          return y
        end
        1
      end

      foo
      CRYSTAL
  end

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

      foo
      CRYSTAL
  end

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

      foo
      CRYSTAL
  end

  it "doesn't fail on Expressions condition (1)" do
    assert_type(<<-CRYSTAL) { nilable int32.metaclass }
      def foo
        if (v = 1; true)
          typeof(v)
        end
      end

      foo
      CRYSTAL
  end

  it "doesn't fail on Expressions condition (2)" do
    assert_type(<<-CRYSTAL) { nilable nil_type.metaclass }
      def foo
        if (v = nil; true)
          typeof(v)
        end
      end

      foo
      CRYSTAL
  end
end
