require "../../spec_helper"

describe "Semantic: alias" do
  it "resolves alias type" do
    assert_type(<<-CRYSTAL) { types["Int32"].metaclass }
      alias Alias = Int32
      Alias
      CRYSTAL
  end

  it "declares alias inside type" do
    assert_type(<<-CRYSTAL) { types["Int32"].metaclass }
      alias Foo::Bar = Int32
      Foo::Bar
      CRYSTAL
  end

  it "works with alias type as restriction" do
    assert_type(<<-CRYSTAL) { int32 }
      alias Alias = Int32

      def foo(x : Alias)
        x
      end

      foo 1
      CRYSTAL
  end

  it "allows using alias type as generic type" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo(T)
        def initialize(x : T)
          @x = x
        end

        def x
          @x
        end
      end

      alias Num = Int32 | Float64

      f = Foo(Num).new(1)
      g = Foo(Num).new(1.5)
      1
      CRYSTAL
  end

  it "allows defining recursive aliases" do
    result = assert_type(<<-CRYSTAL) { int32 }
      class Foo(T)
      end

      alias Alias = Int32 | Foo(Alias)
      1
      CRYSTAL
    mod = result.program

    foo = mod.types["Foo"].as(GenericClassType)
    a = mod.types["Alias"].as(AliasType)

    foo_alias = foo.instantiate([a] of TypeVar)

    aliased_type = a.aliased_type.as(UnionType)
    union_types = aliased_type.union_types.sort_by &.to_s
    union_types[0].should eq(foo_alias)
    union_types[1].should eq(mod.int32)
  end

  it "allows defining recursive fun aliases" do
    result = assert_type(<<-CRYSTAL) { int32 }
      alias Alias = Alias -> Alias
      1
      CRYSTAL

    mod = result.program

    a = mod.types["Alias"].as(AliasType)
    aliased_type = a.aliased_type.as(ProcInstanceType)

    aliased_type.should eq(mod.proc_of(a, a))
  end

  it "allows recursive array with alias" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      alias Type = Nil | Pointer(Type)
      p = Pointer(Type).malloc(1_u64)
      1
      CRYSTAL
  end

  it "errors if alias already defined" do
    assert_error <<-CRYSTAL, "alias Alias is already defined"
      alias Alias = String
      alias Alias = Int32
      CRYSTAL
  end

  it "errors if alias is already defined as another type" do
    assert_error <<-CRYSTAL, "can't alias String because it's already defined as a class"
      alias String = Int32
      CRYSTAL
  end

  it "errors if defining infinite recursive alias" do
    assert_error <<-CRYSTAL, "infinite recursive definition of alias Alias"
      alias Alias = Alias
      Alias
      CRYSTAL
  end

  it "errors if defining infinite recursive alias in union" do
    assert_error <<-CRYSTAL, "infinite recursive definition of alias Alias"
      alias Alias = Int32 | Alias
      Alias
      CRYSTAL
  end

  it "allows using generic type of recursive alias as restriction (#488)" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo(T)
      end

      alias Rec = String | Foo(Rec)

      def command(request : Foo(Rec))
        1
      end

      foo = Foo(Rec).new
      command(foo)
      CRYSTAL
  end

  it "resolves type through alias (#563)" do
    assert_type(<<-CRYSTAL) { int32 }
      module Moo
        Foo = 1
      end

      alias Alias = Moo
      Alias::Foo
      CRYSTAL
  end

  it "errors if trying to resolve type of recursive alias" do
    assert_error <<-CRYSTAL, "undefined constant Rec::A"
      class Foo(T)
        A = 1
      end

      alias Rec = Int32 | Foo(Rec)

      Rec::A
      CRYSTAL
  end

  %w(class module struct).each do |type|
    it "reopens #{type} through alias" do
      assert_type(<<-CRYSTAL) { int32 }
        #{type} Foo
        end

        alias Bar = Foo

        #{type} Bar
          def self.bar
            1
          end
        end

        Bar.bar
        CRYSTAL
    end

    it "reopens #{type} through alias within itself" do
      assert_type <<-CRYSTAL { int32 }
        #{type} Foo
          alias Bar = Foo

          #{type} Bar
            def self.bar
              1
            end
          end
        end

        Foo.bar
        CRYSTAL
    end
  end

  %w(class struct).each do |type|
    it "inherits #{type} through alias" do
      assert_type(<<-CRYSTAL) { int32 }
        abstract #{type} Parent
        end

        alias Alias = Parent

        #{type} Child  < Alias
          def self.bar
            1
          end
        end

        Child.bar
        CRYSTAL
    end
  end

  it "includes module through alias" do
    assert_type(<<-CRYSTAL) { int32 }
      module Moo
        def bar
          1
        end
      end

      alias Alias = Moo

      class Foo
        include Alias
      end

      Foo.new.bar
      CRYSTAL
  end

  it "errors if declares alias inside if" do
    assert_error <<-CRYSTAL, "can't declare alias dynamically"
      if 1 == 2
        alias Foo = Int32
      end
      CRYSTAL
  end

  it "errors if trying to use typeof in alias" do
    assert_error <<-CRYSTAL, "can't use 'typeof' here"
      alias Foo = typeof(1)
      CRYSTAL
  end

  it "can use .class in alias (#2835)" do
    assert_type(<<-CRYSTAL) { union_of(int32.metaclass, string.metaclass).metaclass }
      alias Foo = Int32.class | String.class
      Foo
      CRYSTAL
  end

  it "uses constant in alias (#3259)" do
    assert_type(<<-CRYSTAL) { static_array_of(uint8, 10).metaclass }
      CONST = 10
      alias Alias = UInt8[CONST]
      Alias
      CRYSTAL
  end

  it "uses constant in alias with math (#3259)" do
    assert_type(<<-CRYSTAL) { static_array_of(uint8, 10).metaclass }
      CONST = 2*3 + 4
      alias Alias = UInt8[CONST]
      Alias
      CRYSTAL
  end

  it "looks up alias for macro resolution (#3548)" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        class Bar
          def self.baz
            1
          end
        end
      end

      alias Baz = Foo

      Baz::Bar.baz
      CRYSTAL
  end

  it "finds type through alias (#4645)" do
    assert_type(<<-CRYSTAL) { int32 }
      module FooBar
        module Foo
          A = 10
        end

        module Bar
          include Foo
        end
      end

      class Baz
        alias Bar = FooBar::Bar

        def test
          Bar::A
        end
      end

      Baz.new.test
      CRYSTAL
  end

  it "doesn't find type parameter in alias (#3502)" do
    assert_error <<-CRYSTAL, "undefined constant T"
      class A(T)
        alias B = A(T)
      end
      CRYSTAL
  end

  it "doesn't crash by infinite recursion against type alias and generics (#5329)" do
    assert_error <<-CRYSTAL, "can't cast Foo(Int32) to Bar"
      class Foo(T)
        def initialize(@foo : T)
        end
      end

      alias Bar = Foo(Bar | Int32)

      Foo(Bar).new(Foo.new(1).as(Bar))
      CRYSTAL
  end

  it "can pass recursive alias to proc" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nil_type }
      class Object
        def itself
          self
        end
      end

      alias Rec = Int32 | Array(Rec)

      a = uninitialized Rec

      f = ->(x : Rec) {}
      f.call(a.itself)
      CRYSTAL
  end

  it "overloads union type through alias" do
    assert_type(<<-CRYSTAL) { int32 }
      alias X = Int8 | Int32

      def foo(x : Int32)
        1
      end

      def foo(x : X)
        'a'
      end

      foo(1)
      CRYSTAL
  end
end
