require "../../spec_helper"

describe "Semantic: return" do
  it "infers return type" do
    assert_type("def foo; return 1; end; foo") { int32 }
  end

  it "infers return type with many returns (1)" do
    assert_type("def foo; if true; return 1; end; 'a'; end; foo") { union_of(int32, char) }
  end

  it "infers return type with many returns (2)" do
    assert_type("def foo; if 1 == 1; return 1; end; 'a'; end; foo", inject_primitives: true) { union_of(int32, char) }
  end

  it "errors on return in top level" do
    assert_error "return",
      "can't return from top level"
  end

  it "types return if true" do
    assert_type(<<-CRYSTAL) { nilable int32 }
      def bar
        return if true
        1
      end

      bar
      CRYSTAL
  end

  it "can use type var as return type (#1226)" do
    assert_type(<<-CRYSTAL) { int32 }
      module Moo(T)
      end

      class Foo(T)
        def initialize(@x : T)
        end

        def foo : T
          @x
        end
      end

      Foo.new(1).foo
      CRYSTAL
  end

  it "can use type var as return type with an included generic module" do
    assert_type(<<-CRYSTAL) { float64 }
      module Moo(T)
        def moo : T
          1.5
        end
      end

      class Foo(T)
        include Moo(Float64)

        def initialize(@x : T)
        end
      end

      Foo.new(1).moo
      CRYSTAL
  end

  it "can use type var as return type with an inherited generic class" do
    assert_type(<<-CRYSTAL) { float64 }
      class Moo(T)
        def moo : T
          1.5
        end
      end

      class Foo(T) < Moo(Float64)
        def initialize(@x : T)
        end
      end

      Foo.new(1).moo
      CRYSTAL
  end

  it "doesn't confuse return type from base class" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        class Baz
          def foo
            1
          end
        end

        def x : Baz
          Baz.new
        end
      end

      class Bar < Foo
        class Baz
        end
      end

      Bar.new.x.foo
      CRYSTAL
  end

  it "allows returning NoReturn instead of the wanted type" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        fun exit : NoReturn
      end

      module Moo
        def bar : Int32
          foo
          1
        end
      end

      class Foo
        include Moo

        def foo
          # Not implemented
          LibC.exit
        end
      end

      foo = Foo.new
      foo.bar
      CRYSTAL
  end

  it "types bug (#1823)" do
    assert_type(<<-CRYSTAL) { nilable int32 }
      def test
        b = nil

        begin
        rescue
          b ? return 1 : return 2
        end

        b
      end

      test
      CRYSTAL
  end

  it "allows nilable return type to match subclasses (#1735)" do
    assert_type(<<-CRYSTAL) { nilable types["Bar"] }
      class Foo
      end

      class Bar < Foo
      end

      def test : Foo?
        if true
          Bar.new
        else
          nil
        end
      end

      test
      CRYSTAL
  end

  it "can use free var in return type (#2492)" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { float64 }
      def self.demo(a : A, &block : A -> B) : B forall A, B
        block.call(a)
      end

      z = demo(1) do |x|
        x.to_f
      end
      z
      CRYSTAL
  end

  it "can use non-type free var in return type (#6543)" do
    assert_type(<<-CRYSTAL) { generic_class "Foo", 1.int32 }
      class Foo(A)
      end

      def foo(a : Foo(P)) : Foo(P) forall P
        a
      end

      foo(Foo(1).new)
      CRYSTAL
  end

  it "can use non-type free var in return type (2) (#6543)" do
    assert_type(<<-CRYSTAL) { generic_class "Matrix", 3.int32, 4.int32 }
      class Matrix(N, M)
        def *(other : Matrix(M, P)) : Matrix(N, P) forall P
          Matrix(N, P).new
        end
      end

      Matrix(3, 2).new * Matrix(2, 4).new
      CRYSTAL
  end

  it "errors if non-type free var cannot be inferred" do
    assert_error <<-CRYSTAL, "undefined constant P"
      class Foo(A)
      end

      def foo(a) : Foo(P) forall P
        a
      end

      foo(Foo(1).new)
      CRYSTAL
  end

  it "forms a tuple from multiple return values" do
    assert_type("def foo; return 1, 1.0; end; foo") { tuple_of([int32, float64]) }
  end

  it "flattens splats inside multiple return values" do
    assert_type("def foo; return 1, *{1.0, 'a'}, true; end; foo") { tuple_of([int32, float64, char, bool]) }
  end
end
