require "../../spec_helper"

describe "Semantic: enum" do
  it "types enum" do
    assert_type(<<-CRYSTAL) { types["Foo"] }
      enum Foo
        A = 1
      end
      Foo::A
      CRYSTAL
  end

  it "types enum value" do
    assert_type(<<-CRYSTAL) { int32 }
      enum Foo
        A = 1
      end
      Foo::A.value
      CRYSTAL
  end

  it "disallows implicit conversion of int to enum" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be Foo, not Int32"
      enum Foo
        A = 1
      end

      def foo(x : Foo)
      end

      foo 1
      CRYSTAL
  end

  it "finds method in enum type" do
    assert_type(<<-CRYSTAL) { int32 }
      struct Enum
        def foo
          1
        end
      end

      enum Foo
        A = 1
      end

      Foo::A.foo
      CRYSTAL
  end

  it "finds class method in enum type" do
    assert_type(<<-CRYSTAL) { int32 }
      struct Enum
        def self.foo
          1
        end
      end

      enum Foo
        A = 1
      end

      Foo.foo
      CRYSTAL
  end

  it "errors if using a name twice" do
    assert_error <<-CRYSTAL, "enum 'Foo' already contains a member named 'A'"
      enum Foo
        A
        A
      end
      CRYSTAL
  end

  it "creates enum from value" do
    assert_type(<<-CRYSTAL) { types["Foo"] }
      enum Foo
        A
        B
      end

      Foo.new(1)
      CRYSTAL
  end

  it "defines method on enum" do
    assert_type(<<-CRYSTAL) { int32 }
      enum Foo
        A
        B

        def foo
          1
        end
      end

      Foo::A.foo
      CRYSTAL
  end

  it "defines class method on enum" do
    assert_type(<<-CRYSTAL) { int32 }
      enum Foo
        A
        B

        def self.foo
          1
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "reopens an enum" do
    assert_type(<<-CRYSTAL) { int32 }
      enum Foo
        A
        B
      end

      enum Foo
        def foo
          1
        end
      end

      Foo::A.foo
      CRYSTAL
  end

  it "errors if reopen but not enum" do
    assert_error <<-CRYSTAL, "Foo is not a enum, it's a class"
      class Foo
      end

      enum Foo
        A
        B
      end
      CRYSTAL
  end

  it "errors if reopen and tries to define constant" do
    assert_error <<-CRYSTAL, "can't reopen enum and add more constants to it"
      enum Foo
        A
        B
      end

      enum Foo
        C
      end
      CRYSTAL
  end

  it "has None value when defined as @[Flags]" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Flags]
      enum Foo
        A
        B
      end

      Foo::None.value
      CRYSTAL
  end

  it "has All value when defined as @[Flags]" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Flags]
      enum Foo
        A
        B
      end

      Foo::All.value
      CRYSTAL
  end

  it "doesn't break assigned values in enum flags when a member has value 0 (#5767)" do
    result = semantic(<<-CRYSTAL)
      @[Flags]
      enum Foo
        OtherNone = 0
        Bar
        Baz
      end
      CRYSTAL
    enum_type = result.program.types["Foo"].as(EnumType)
    enum_type.types["OtherNone"].as(Const).value.should eq(NumberLiteral.new("0", :i32))
    enum_type.types["Bar"].as(Const).value.should eq(NumberLiteral.new("1", :i32))
    enum_type.types["Baz"].as(Const).value.should eq(NumberLiteral.new("2", :i32))
  end

  it "disallows redefining None to non-0 for @[Flags] enum" do
    assert_error <<-CRYSTAL, "flags enum can't redefine None member to non-0"
      @[Flags]
      enum Foo
        None = 42
        Dummy
      end
      CRYSTAL

    assert_error <<-CRYSTAL, "flags enum can't redefine None member to non-0"
      @[Flags]
      enum Foo
        None    # 1
        Dummy
      end
      CRYSTAL
  end

  it "allows redefining None to 0 for @[Flags] enum" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Flags]
      enum Foo
        None = 0
        Dummy
      end

      Foo::None.value
      CRYSTAL
  end

  it "disallows All value for @[Flags] enum" do
    assert_error <<-CRYSTAL, "flags enum can't redefine All member"
      @[Flags]
      enum Foo
        All = 50
      end
      CRYSTAL
  end

  it "doesn't error when defining a non-flags enum with None or All" do
    assert_type(<<-CRYSTAL) { int32 }
      enum Foo
        None
        All = 50
      end

      Foo::None.value
      CRYSTAL
  end

  it "doesn't error when defining a flags enum in a lib with None or All" do
    assert_type(<<-CRYSTAL) { int32 }
      lib Lib
        @[Flags]
        enum Foo
          None
          Dummy
          All = 50
        end
      end

      Lib::Foo::None.value
      CRYSTAL
  end

  it "doesn't error when defining a method for an enum with flags" do
    assert_type(<<-CRYSTAL) { types["Foo"] }
      @[Flags]
      enum Foo
        A
        B

        def foo
          self
        end
      end

      Foo::A.foo
      CRYSTAL
  end

  it "allows class vars in enum" do
    assert_type(<<-CRYSTAL) { int32 }
      enum Foo
        A

        @@class_var = 1

        def self.class_var
          @@class_var
        end
      end

      Foo.class_var
      CRYSTAL
  end

  it "errors if invoking private enum method" do
    assert_error <<-CRYSTAL, "private method 'foo' called for Foo"
      enum Foo
        A

        private def foo
          1
        end
      end

      Foo::A.foo
      CRYSTAL
  end

  it "errors if enum value is too big for type (#678)" do
    assert_error <<-CRYSTAL, "invalid Int32: 2147486719"
      enum Foo
        A = 2147486719
      end
      CRYSTAL
  end

  it "reports arithmetic overflow in enum value expression (#11746)" do
    assert_error <<-CRYSTAL, "Arithmetic overflow", inject_primitives: true
      enum Foo : UInt64
        A = 0_u64 - 1_u64
      end
      CRYSTAL
  end

  it "errors if using instance var inside enum (#991)" do
    assert_error <<-CRYSTAL, "can't use instance variables inside enums (at enum Foo)"
      enum Foo
        A

        def meth
          puts @value
        end
      end

      Foo::A.meth
      CRYSTAL
  end

  it "marks as flags with base type (#2185)" do
    result = semantic(<<-CRYSTAL)
      @[Flags]
      enum SomeFacts : UInt8
        AppleLover
        PearLover
        CoolDude
      end

      SomeFacts::AppleLover
      CRYSTAL
    enum_type = result.program.types["SomeFacts"].as(EnumType)
    annotation_type = result.program.types["Flags"].as(AnnotationType)
    enum_type.annotation(annotation_type).should_not be_nil
  end

  it "reopens enum without base type (1)" do
    assert_no_errors <<-CRYSTAL
      enum Foo
        X
      end

      enum Foo
      end
      CRYSTAL
  end

  it "reopens enum without base type (2)" do
    assert_no_errors <<-CRYSTAL
      enum Foo : UInt8
        X
      end

      enum Foo
      end
      CRYSTAL
  end

  it "reopens enum with same base type (1)" do
    assert_no_errors <<-CRYSTAL
      enum Foo
        X
      end

      enum Foo : Int32
      end
      CRYSTAL
  end

  it "reopens enum with same base type (2)" do
    assert_no_errors <<-CRYSTAL
      enum Foo : UInt8
        X
      end

      enum Foo : UInt8
      end
      CRYSTAL
  end

  it "errors if reopening enum with different base type (1)" do
    assert_error <<-CRYSTAL, "enum Foo's base type is Int32, not UInt8"
      enum Foo
        X
      end

      enum Foo : UInt8
      end
      CRYSTAL
  end

  it "errors if reopening enum with different base type (2)" do
    assert_error <<-CRYSTAL, "enum Foo's base type is UInt8, not UInt16"
      enum Foo : UInt8
        X
      end

      enum Foo : UInt16
      end
      CRYSTAL
  end

  it "can use macro expression inside enum" do
    assert_type(<<-CRYSTAL) { types["Foo"] }
      enum Foo
        {{ "A".id }}
      end

      Foo::A
      CRYSTAL
  end

  it "can use macro for inside enum" do
    assert_type(<<-CRYSTAL) { types["Foo"] }
      enum Foo
        {% for name in %w(A B C) %}
          {{name.id}}
        {% end %}
      end

      Foo::A
      CRYSTAL
  end

  it "errors if inheriting Enum (#3592)" do
    assert_error <<-CRYSTAL, "can't inherit Enum. Use the enum keyword to define enums"
      struct Foo < Enum
      end
      CRYSTAL
  end

  it "errors on enum without members (#3447)" do
    assert_error <<-CRYSTAL, "enum Foo must have at least one member"
      enum Foo
      end
      CRYSTAL

    assert_error <<-CRYSTAL, "enum Foo must have at least one member"
      @[Flags]
      enum Foo
        None = 0
      end
      CRYSTAL
  end

  it "errors if declaring type inside enum (#3127)" do
    assert_error <<-CRYSTAL, "can't declare type inside enum Foo"
      enum Foo
        A
      end

      class Foo::Bar
      end
      CRYSTAL
  end

  it "errors if declaring type inside enum, nested (#3127)" do
    assert_error <<-CRYSTAL, "can't declare type inside enum"
      enum Foo
        A
      end

      class Foo::Bar::Baz
      end
      CRYSTAL
  end

  it "attaches annotation to enum method (#6690)" do
    result = semantic(<<-CRYSTAL)
      enum Foo
        X

        @[AlwaysInline]
        def bar
        end
      end
      CRYSTAL

    method = result.program.types["Foo"].lookup_first_def("bar", block: false).not_nil!
    method.always_inline?.should be_true
  end

  it "errors if defining initialize in Enum (#7238)" do
    assert_error <<-CRYSTAL, "enums can't define an `initialize` method, try using `def self.new`"
      enum Foo
        FOO = 1

        def initialize
        end
      end
      CRYSTAL
  end

  it "can redefine Enum.new" do
    assert_type(<<-CRYSTAL) { string }
      enum Foo
        FOO = 1

        def self.new(x : Int32)
          "hello"
        end
      end

      Foo.new(1)
      CRYSTAL
  end

  it "gives error on enum overflow" do
    assert_error <<-CRYSTAL, "value of enum member V129 would overflow the base type Int8"
      enum Foo : Int8
        #{Array.new(129) { |i| "V#{i + 1}" }.join "\n"}
      end
      CRYSTAL
  end

  it "gives error on flags enum overflow" do
    assert_error <<-CRYSTAL, "value of enum member V9 would overflow the base type UInt8"
      @[Flags]
      enum Foo : UInt8
        #{Array.new(9) { |i| "V#{i + 1}" }.join "\n"}
      end
      CRYSTAL
  end

  it "gives error on enum overflow after a member with value" do
    assert_error <<-CRYSTAL, "value of enum member B would overflow the base type Int32"
      enum Foo
        A = 0x7FFFFFFF
        B
      end
      CRYSTAL
  end

  it "gives error on signed flags enum overflow after a member with value" do
    assert_error <<-CRYSTAL, "value of enum member B would overflow the base type Int32"
      @[Flags]
      enum Foo
        A = 0x40000000
        B
      end
      CRYSTAL
  end

  it "gives error on unsigned flags enum overflow after a member with value" do
    assert_error <<-CRYSTAL, "value of enum member B would overflow the base type UInt32"
      @[Flags]
      enum Foo : UInt32
        A = 0x80000000
        B
      end
      CRYSTAL
  end

  it "doesn't overflow when going from negative to zero (#7874)" do
    assert_no_errors <<-CRYSTAL
      enum Nums
        Zero  = -2
        One
        Two
      end
      CRYSTAL
  end

  it "doesn't overflow on flags member (#7877)" do
    assert_no_errors <<-CRYSTAL
      @[Flags]
      enum Filter
        A = 1 << 29
        B
      end
      CRYSTAL
  end

  it "doesn't visit enum members generated by macros twice (#10104)" do
    result = semantic(<<-CRYSTAL)
      enum Foo
        A = 1

        {% begin %}
          def foo
          end
        {% end %}
      end
      CRYSTAL
    a_def = result.program.types["Foo"].lookup_defs("foo").first
    a_def.previous.should be_nil
  end

  it "adds docs to helper methods" do
    result = top_level_semantic <<-CRYSTAL, wants_doc: true
    enum Foo
      # These are the docs for `Bar`
      Bar = 1
    end
    CRYSTAL

    a_defs = result.program.types["Foo"].lookup_defs("bar?")
    a_defs.first.doc.should eq("Returns `true` if this enum value equals `Bar`")
  end

  it "marks helper methods with `:nodoc:` if the member is `:nodoc:`" do
    result = top_level_semantic <<-CRYSTAL, wants_doc: true
    enum Foo
      # :nodoc:
      Bar = 1
    end
    CRYSTAL

    a_defs = result.program.types["Foo"].lookup_defs("bar?")
    a_defs.first.doc.should eq(":nodoc:")
  end
end
