require "../../spec_helper"

describe "Semantic: def" do
  it "types a call with an int" do
    assert_type("def foo; 1; end; foo") { int32 }
  end

  it "types a call with a float" do
    assert_type("def foo; 2.3f32; end; foo") { float32 }
  end

  it "types a call with a double" do
    assert_type("def foo; 2.3; end; foo") { float64 }
  end

  it "types a call with an argument" do
    assert_type("def foo(x); x; end; foo 1") { int32 }
  end

  it "types a call with an argument" do
    input = parse "def foo(x); x; end; foo 1; foo 2.3"
    result = semantic input
    mod, input = result.program, result.node.as(Expressions)

    input[1].type.should eq(mod.int32)
    input[2].type.should eq(mod.float64)
  end

  it "types a call with an argument uses a new scope" do
    assert_type("x = 2.3; def foo(x); x; end; foo 1; x") { float64 }
  end

  it "assigns def owner" do
    input = parse "struct Int; def foo; 2.5; end; end; 1.foo"
    result = semantic input
    mod, input = result.program, result.node.as(Expressions)
    input.last.as(Call).target_def.owner.should eq(mod.int32)
  end

  it "types putchar with Char" do
    assert_type("lib LibC; fun putchar(c : Char) : Char; end; LibC.putchar 'a'") { char }
  end

  it "types getchar with Char" do
    assert_type("lib LibC; fun getchar : Char; end; LibC.getchar") { char }
  end

  it "allows recursion" do
    assert_type("def foo; foo; end; foo") { no_return }
  end

  it "allows recursion with arg" do
    assert_type("def foo(x); foo(x); end; foo 1") { no_return }
  end

  it "types simple recursion" do
    assert_type("def foo(x); if x > 0; foo(x - 1) + 1; else; 1; end; end; foo(5)", inject_primitives: true) { int32 }
  end

  it "types simple recursion 2" do
    assert_type("def foo(x); if x > 0; 1 + foo(x - 1); else; 1; end; end; foo(5)", inject_primitives: true) { int32 }
  end

  it "types mutual recursion" do
    assert_type("def foo(x); if 1 == 1; bar(x); else; 1; end; end; def bar(x); foo(x); end; foo(5)", inject_primitives: true) { int32 }
  end

  it "types empty body def" do
    assert_type("def foo; end; foo") { nil_type }
  end

  it "types mutual infinite recursion" do
    assert_type("def foo; bar; end; def bar; foo; end; foo") { no_return }
  end

  it "types call with union argument" do
    assert_type("def foo(x); x; end; a = 1 || 1.1; foo(a)") { union_of(int32, float64) }
  end

  it "defines class method" do
    assert_type("def Int.foo; 2.5; end; Int.foo") { float64 }
  end

  it "defines class method with self" do
    assert_type("struct Int; def self.foo; 2.5; end; end; Int.foo") { float64 }
  end

  it "calls with default argument" do
    assert_type("def foo(x = 1); x; end; foo") { int32 }
  end

  it "do not use body for the def type" do
    input = parse <<-CRYSTAL
      require "primitives"

      def foo
        if 1 == 2
          return 0
        end
      end

      foo
      CRYSTAL
    result = semantic input
    mod, input = result.program, result.node.as(Expressions)

    call = input.last.as(Call)
    call.type.should eq(mod.nilable(mod.int32))
    call.target_def.body.type.should eq(mod.nil)
  end

  it "reports undefined method" do
    assert_error "foo()",
      "undefined method 'foo'"
  end

  it "reports no overload matches" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be Int, not (Float64 | Int32)"
      def foo(x : Int)
      end

      foo 1 || 1.5
      CRYSTAL
  end

  it "reports no overload matches 2" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be Int, not (Char | Int32)"
      def foo(x : Int, y : Int)
      end

      def foo(x : Int, y : Float)
      end

      foo(1 || 'a', 1 || 1.5)
      CRYSTAL
  end

  it "reports no block given" do
    assert_error <<-CRYSTAL, "'foo' is expected to be invoked with a block, but no block was given"
      def foo
        yield
      end

      foo
      CRYSTAL
  end

  it "reports block given" do
    assert_error <<-CRYSTAL, "'foo' is not expected to be invoked with a block, but a block was given"
      def foo
      end

      foo {}
      CRYSTAL
  end

  it "errors when calling two functions with nil type" do
    assert_error <<-CRYSTAL, "undefined method"
      def bar
      end

      def foo
      end

      foo.bar
      CRYSTAL
  end

  it "errors when default value is incompatible with type restriction" do
    assert_error <<-CRYSTAL, "can't restrict Char to Int64"
      def foo(x : Int64 = 'a')
      end

      foo
      CRYSTAL
  end

  it "errors when default value is incompatible with non-type restriction" do
    assert_error <<-CRYSTAL, "can't restrict Char to Tuple(_)"
      def foo(x : Tuple(_) = 'a')
      end

      foo
      CRYSTAL
  end

  it "types call with global scope" do
    assert_type(<<-CRYSTAL) { int32 }
      def bar
        1
      end

      class Foo
        def foo
          ::bar
        end

        def bar
          'a'
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "lookups methods in super modules" do
    assert_type(<<-CRYSTAL) { int32 }
      require "prelude"

      module Foo
        def lookup_matches(x = 1)
          1
        end
      end

      module Bar
        include Foo
      end

      abstract class Type
      end

      abstract class CType < Type
      end

      abstract class MType < CType
        include Bar
      end

      class NonGenericMType < MType
      end

      class GenericMType < MType
      end

      b = [] of Type
      b.push NonGenericMType.new
      b.push GenericMType.new
      b[0].lookup_matches
      CRYSTAL
  end

  it "fixes bug #165" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be Pointer(Node), not Node", inject_primitives: true
      abstract class Node
      end

      def foo(nodes : Pointer(Node))
        foo nodes.value
      end

      a = Pointer(Node).new(0_u64)
      foo a
      CRYSTAL
  end

  it "says can only defined def on types and self" do
    assert_error <<-CRYSTAL, "def receiver can only be a Type or self"
      class Foo
      end

      foo = Foo.new
      def foo.bar
      end
      CRYSTAL
  end

  it "errors if return type doesn't match" do
    assert_error <<-CRYSTAL, "method ::foo must return Int32 but it is returning Char"
      def foo : Int32
        'a'
      end

      foo
      CRYSTAL
  end

  it "errors if return type doesn't match on instance method" do
    assert_error <<-CRYSTAL, "method Foo#foo must return Int32 but it is returning Char"
      class Foo
        def foo : Int32
          'a'
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "errors if return type doesn't match on class method" do
    assert_error <<-CRYSTAL, "method Foo.foo must return Int32 but it is returning Char"
      class Foo
        def self.foo : Int32
          'a'
        end
      end

      Foo.foo
      CRYSTAL
  end

  it "is ok if returns Int32? with explicit return" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { nilable int32 }
      def foo : Int32?
        if 1 == 2
          return nil
        end
        1
      end

      foo
      CRYSTAL
  end

  it "says compile-time type on error" do
    assert_error <<-CRYSTAL, "compile-time type is Foo+"
      abstract class Foo
      end

      class Bar < Foo
        def bar
          1
        end
      end

      class Baz < Foo
      end

      f = Bar.new || Baz.new
      f.bar
      CRYSTAL
  end

  it "gives correct error for wrong number of arguments for program call inside type (#1024)" do
    assert_error <<-CRYSTAL, "wrong number of arguments for 'foo' (given 1, expected 0)"
      def foo
      end

      class Foo
        def self.bar
          foo 1
        end
      end

      Foo.bar
      CRYSTAL
  end

  it "gives correct error for wrong number of arguments for program call inside type (2) (#1024)" do
    assert_error <<-CRYSTAL, "expected argument #1 to 'foo' to be String, not Int32"
      def foo(x : String)
      end

      class Foo
        def self.bar
          foo 1
        end
      end

      Foo.bar
      CRYSTAL
  end

  it "gives correct error for methods in Class" do
    assert_error <<-CRYSTAL, <<-ERROR
      class Class
        def foo
          1
        end
      end

      class Foo
      end

      Foo.foo(1)
      CRYSTAL
      wrong number of arguments for 'Foo.foo' (given 1, expected 0)

      Overloads are:
       - Class#foo()
      ERROR
  end

  it "gives correct error for methods in Class (2)" do
    assert_error <<-CRYSTAL, <<-ERROR
      class Class
        def self.foo
          1
        end
      end

      class Foo
      end

      Foo.foo(1)
      CRYSTAL
      wrong number of arguments for 'Foo.foo' (given 1, expected 0)

      Overloads are:
       - Class#foo()
      ERROR
  end

  it "errors if declares def inside if" do
    assert_error <<-CRYSTAL, "can't declare def dynamically"
      if 1 == 2
        def foo; end
      end
      CRYSTAL
  end

  it "accesses free var of default argument (#1101)" do
    assert_type(<<-CRYSTAL) { nil_type.metaclass }
      def foo(x, y : U = nil) forall U
        U
      end

      foo 1
      CRYSTAL
  end

  it "clones regex literal value (#2384)" do
    assert_type(<<-CRYSTAL) { int32 }
      require "prelude"

      def foo(x : String = "")
        /\#{1}/
        10
      end

      foo
      foo("")
      CRYSTAL
  end

  it "doesn't find type in namespace through free var" do
    assert_error <<-CRYSTAL, "undefined constant T::String"
      def foo(x : T) forall T
        T::String
      end

      foo(1)
      CRYSTAL
  end

  it "errors if trying to declare method on generic class instance" do
    assert_error <<-CRYSTAL, "can't define method in generic instance"
      class Foo(T)
      end

      alias Bar = Foo(Int32)

      def Bar.foo
      end
      CRYSTAL
  end

  it "uses free variable" do
    assert_type(<<-CRYSTAL) { int32.metaclass }
      def foo(x : Free) forall Free
        Free
      end

      foo(1)
      CRYSTAL
  end

  it "uses free variable with metaclass" do
    assert_type(<<-CRYSTAL) { int32.metaclass }
      def foo(x : Free.class) forall Free
        Free
      end

      foo(Int32)
      CRYSTAL
  end

  it "uses free variable with metaclass and default value" do
    assert_type(<<-CRYSTAL) { int32.metaclass }
      def foo(x : Free.class = Int32) forall Free
        Free
      end

      foo
      CRYSTAL
  end

  it "uses free variable as block return type" do
    assert_type(<<-CRYSTAL) { int32.metaclass }
      def foo(&block : -> Free) forall Free
        yield
        Free
      end

      foo { 1 }
      CRYSTAL
  end

  it "uses free variable and doesn't conflict with top-level type" do
    assert_type(<<-CRYSTAL) { int32.metaclass }
      class Free
      end

      def foo(x : Free) forall Free
        Free
      end

      foo(1)
      CRYSTAL
  end

  it "shows free variables if no overload matches" do
    assert_error <<-CRYSTAL, <<-ERROR
      class Foo(T)
        def foo(x : T, y : U, z : V) forall U, V
        end
      end

      Foo(Int32).new.foo("", "", "")
      CRYSTAL
      Overloads are:
       - Foo(T)#foo(x : T, y : U, z : V) forall U, V
      ERROR
  end

  it "can't use self in toplevel method" do
    assert_error <<-CRYSTAL, "there's no self in this scope"
      def foo
        self
      end

      foo
      CRYSTAL
  end

  it "points error at name (#6937)" do
    ex = assert_error <<-CRYSTAL,
      1.
        foobar
      CRYSTAL
      "undefined method"
    ex.line_number.should eq(2)
    ex.column_number.should eq(3)
    ex.size.should eq(6)
  end
end
