require "../../spec_helper"

describe "semantic: case" do
  it "doesn't check exhaustiveness when using 'when'" do
    assert_no_errors <<-CRYSTAL
      a = 1 || nil
      case a
      when Int32
      end
      CRYSTAL
  end

  it "checks exhaustiveness of single type" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing types:\n - Int32"
      case 1
      in Nil
      end
      CRYSTAL
  end

  it "checks exhaustiveness of single type (T.class)" do
    assert_no_errors <<-CRYSTAL
      case Int32
      in Int32.class
      end
      CRYSTAL
  end

  it "checks exhaustiveness of single type (Foo(T).class)" do
    assert_no_errors <<-CRYSTAL
      class Foo(T)
      end

      case Foo(Int32)
      in Foo(Int32).class
      end
      CRYSTAL
  end

  it "checks exhaustiveness of single type (generic)" do
    assert_no_errors <<-CRYSTAL
      class Foo(T)
      end

      case Foo(Int32).new
      in Foo(Int32)
      end
      CRYSTAL
  end

  it "errors if casing against a constant" do
    assert_error <<-CRYSTAL, "can't use constant values in exhaustive case, only constant types"
      #{bool_case_eq}

      FOO = false

      case true
      in FOO
      end
      CRYSTAL
  end

  it "covers all types" do
    assert_no_errors <<-CRYSTAL
      a = 1 || nil
      case a
      in Int32
      in Nil
      end
      CRYSTAL
  end

  it "checks exhaustiveness of bool type (missing true)" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - true"
      #{bool_case_eq}

      case false
      in false
      end
      CRYSTAL
  end

  it "checks exhaustiveness of bool type (missing false)" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - false"
      #{bool_case_eq}

      case false
      in true
      end
      CRYSTAL
  end

  it "checks exhaustiveness of enum via question method" do
    assert_error <<-CRYSTAL, "case is not exhaustive for enum Color.\n\nMissing members:\n - Green\n - Blue", inject_primitives: true
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red
      case e
      in .red?
      end
      CRYSTAL
  end

  it "checks exhaustiveness of enum via const" do
    assert_error <<-CRYSTAL, "case is not exhaustive for enum Color.\n\nMissing members:\n - Green\n - Blue"
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red
      case e
      in Color::Red
      end
      CRYSTAL
  end

  it "checks exhaustiveness of enum (all cases covered)" do
    assert_no_errors <<-CRYSTAL
      require "prelude"

      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red
      case e
      in .red?
      in .green?
      in .blue?
      end
      CRYSTAL
  end

  it "checks exhaustiveness of enum through method (all cases covered)" do
    assert_no_errors <<-CRYSTAL
      require "prelude"

      enum Color
        Red
        Green
        Blue
      end

      def foo
        Color::Red
      end

      case foo
      in .red?
      in .green?
      in .blue?
      end
      CRYSTAL
  end

  it "checks exhaustiveness of bool type with other types" do
    assert_no_errors <<-CRYSTAL
      #{bool_case_eq}

      case 1 || true
      in Int32
      in true
      in false
      end
      CRYSTAL
  end

  it "checks exhaustiveness of union type with virtual type" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing types:\n - Int32"
      class Foo
      end

      class Bar < Foo
      end

      a = 1 || Foo.new || Bar.new
      case a
      in Foo
      end
      CRYSTAL
  end

  it "checks exhaustiveness, covers in base type covers" do
    assert_no_errors <<-CRYSTAL
      class Foo
      end

      class Bar < Foo
      end

      a = Bar.new
      case a
      in Foo
      end
      CRYSTAL
  end

  it "checks exhaustiveness, covers in base type covers (generic type)" do
    assert_no_errors <<-CRYSTAL
      class Foo(T)
      end

      a = Foo(Int32).new
      case a
      in Foo
      end
      CRYSTAL
  end

  it "checks exhaustiveness of nil type with nil literal" do
    assert_no_errors <<-CRYSTAL
      struct Nil
        def ===(other)
          true
        end
      end

      case nil
      in nil
      end
      CRYSTAL
  end

  it "checks exhaustiveness of nilable type with nil literal" do
    assert_no_errors <<-CRYSTAL
      struct Nil
        def ===(other)
          true
        end
      end

      a = 1 || nil
      case a
      in nil
      in Int32
      end
      CRYSTAL
  end

  it "can't prove case is exhaustive for @[Flags] enum" do
    assert_error <<-CRYSTAL, <<-ERROR
      #{enum_eq}

      struct Enum
        def includes?(other : self)
          false
        end
      end

      @[Flags]
      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red
      case e
      in .red?
      end
      CRYSTAL
      case is not exhaustive.

      Missing cases:
       - Color

      Note that @[Flags] enum can't be proved to be exhaustive by matching against enum members.
      In particular, the enum Color can't be proved to be exhaustive like that.
      ERROR
  end

  it "can prove case is exhaustive for @[Flags] enum when matching type" do
    assert_no_errors <<-CRYSTAL
      require "prelude"

      @[Flags]
      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red
      case e
      in Color
      end
      CRYSTAL
  end

  it "can't prove case is exhaustive for @[Flags] enum, tuple case" do
    assert_error <<-CRYSTAL, <<-ERROR
      #{enum_eq}

      struct Enum
        def includes?(other : self)
          false
        end
      end

      @[Flags]
      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red
      case {e}
      in {.red?}
      end
      CRYSTAL
      case is not exhaustive.

      Missing cases:
       - {Color}

      Note that @[Flags] enum can't be proved to be exhaustive by matching against enum members.
      In particular, the enum Color can't be proved to be exhaustive like that.
      ERROR
  end

  it "checks exhaustiveness of enum combined with another type" do
    assert_error <<-CRYSTAL, "case is not exhaustive for enum Color.\n\nMissing members:\n - Green\n - Blue", inject_primitives: true
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      e = Color::Red || 1
      case e
      in Int32
      in .red?
      end
      CRYSTAL
  end

  it "checks exhaustiveness of union with bool" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - false\n - Int32"
      #{bool_case_eq}

      e = 1 || true
      case e
      in true
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal, and passes" do
    assert_no_errors <<-CRYSTAL
      a = 1 || 'a'
      b = 1 || 'a'

      case {a, b}
      in {Int32, Char}
      in {Int32, Int32}
      in {Char, Int32}
      in {Char, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal of 2 elements, and warns" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Int32}"
      a = 1 || 'a'

      case {a, a}
      in {Int32, Char}
      in {Int32, Int32}
      in {Char, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal of 3 elements, and warns" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Int32, Char}\n - {Int32, Int32, Char}"
      a = 1 || 'a'

      case {a, a, a}
      in {Int32, Int32, Int32}
      in {Int32, Char, Int32}
      in {Int32, Char, Char}
      in {Char, Int32, Int32}
      in {Char, Char, Int32}
      in {Char, Char, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal of 2 elements, first is bool" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {false, Char}"
      #{bool_case_eq}

      case {true, 'a'}
      in {true, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal of 3 elements, all bool" do
    assert_error <<-CRYSTAL, <<-ERROR
      #{bool_case_eq}

      case {true, true, true}
      in {true, true, true}
      end
      CRYSTAL
      case is not exhaustive.

      Missing cases:
       - {true, true, false}
       - {true, false, Bool}
       - {false, Bool, Bool}
      ERROR
  end

  it "checks exhaustiveness for tuple literal of 2 elements, first is enum" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Color::Green, Char}", inject_primitives: true
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      case {Color::Red, 'a'}
      in {.red?, Char}
      in {.blue?, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal of 3 elements, all enums" do
    assert_error <<-CRYSTAL, <<-ERROR, inject_primitives: true
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      case {Color::Red, Color::Red, Color::Red}
      in {.red?, .green?, .blue?}
      end
      CRYSTAL
      case is not exhaustive.

      Missing cases:
       - {Color::Red, Color::Red, Color}
       - {Color::Red, Color::Green, Color::Red}
       - {Color::Red, Color::Green, Color::Green}
       - {Color::Red, Color::Blue, Color}
       - {Color::Green, Color, Color}
       - {Color::Blue, Color, Color}
      ERROR
  end

  it "checks exhaustiveness for tuple literal with types and underscore at first position" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Char}\n - {Int32, Char}"
      a = 1 || 'a'

      case {a, a}
      in {_, Int32}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with types and underscore at second position" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Char}\n - {Char, Int32}"
      a = 1 || 'a'

      case {a, a}
      in {Int32, _}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at first position" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Bool, Char}"
      #{bool_case_eq}

      case {true, 1 || 'a'}
      in {_, Int32}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at first position, with partial match" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {true, Char}"
      #{bool_case_eq}

      case {true, 1 || 'a'}
      in {_, Int32}
      in {false, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at second position" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Bool}"
      #{bool_case_eq}

      case {1 || 'a', true}
      in {Int32, _}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at second position, with partial match" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, true}"
      #{bool_case_eq}

      case {1 || 'a', true}
      in {Int32, _}
      in {Char, false}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at first position" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Color, Char}"
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      case {Color::Red, 1 || 'a'}
      in {_, Int32}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at first position, partial match" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Color::Red, Char}\n - {Color::Green, Char}", inject_primitives: true
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      case {Color::Red, 1 || 'a'}
      in {_, Int32}
      in {.blue?, Char}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at second position" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Color}"
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      case {1 || 'a', Color::Red}
      in {Int32, _}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal with bool and underscore at second position, partial match" do
    assert_error <<-CRYSTAL, "case is not exhaustive.\n\nMissing cases:\n - {Char, Color::Red}\n - {Char, Color::Green}", inject_primitives: true
      #{enum_eq}

      enum Color
        Red
        Green
        Blue
      end

      case {1 || 'a', Color::Red}
      in {Int32, _}
      in {Char, .blue?}
      end
      CRYSTAL
  end

  it "checks exhaustiveness for tuple literal, with call" do
    assert_no_errors <<-CRYSTAL
      struct Int
        def bar
          1 || 'a'
        end
      end

      foo = 1

      case {foo.bar, foo.bar}
      in {Int32, Char}
      in {Int32, Int32}
      in {Char, Int32}
      in {Char, Char}
      end
      CRYSTAL
  end
end

private def bool_case_eq
  <<-CRYSTAL
  struct Bool
    def ===(other)
      true
    end
  end
  CRYSTAL
end

private def enum_eq
  <<-CRYSTAL
  struct Enum
    def ==(other : self)
      value == other.value
    end

    def ===(other)
      true
    end
  end
  CRYSTAL
end
