require "../../spec_helper"

private def assert_concrete_types(str)
  result = semantic_result("struct Witness;end\n\n#{str}", nil, inject_primitives: false)
  program = result.program

  type, expected_concrete_types = yield program.types, program

  if type.responds_to?(:concrete_types)
    Set.new(type.concrete_types).should eq(Set.new(expected_concrete_types))
  elsif type.is_a?(ModuleType) || type.is_a?(GenericModuleInstanceType)
    # Modules are not MultiType, we check only using the witness
  else
    fail "#{type} : #{type.class} does not responds to :concrete_types"
  end

  # We enforce that the same results are expected for a concrete types
  # with respect a union of Witness. See UnionType#each_concrete_type
  witness_type = program.types["Witness"]
  wrapped_union = program.union_of([witness_type] of Type + (type.is_a?(UnionType) ? type.union_types : [type] of Type)).as(UnionType)
  Set.new(wrapped_union.concrete_types).should eq(Set.new(expected_concrete_types).add(witness_type))
end

describe "Semantic: concrete_types" do
  it "UnionType of structs" do
    assert_concrete_types(%(
      struct Foo
      end

      struct Bar
      end
    )) do |t, p|
      {p.union_of(t["Foo"], t["Bar"]), [t["Foo"], t["Bar"]]}
    end
  end

  it "VirtualType with abstract base" do
    assert_concrete_types(%(
      abstract class Base
      end

      class Foo < Base
      end

      class Bar < Base
      end
    )) do |t|
      {t["Base"].virtual_type, [t["Foo"], t["Bar"]]}
    end
  end

  it "VirtualType with concrete base" do
    assert_concrete_types(%(
      class Base
      end

      class Foo < Base
      end

      class Bar < Base
      end
    )) do |t|
      {t["Base"].virtual_type, [t["Base"], t["Foo"], t["Bar"]]}
    end
  end

  it "VirtualMetaclassType with abstract base" do
    assert_concrete_types(%(
      abstract class Base
      end

      class Foo < Base
      end

      class Bar < Base
      end
    )) do |t|
      # abstract base class are required because the metaclass can allways be used: Base.method
      {t["Base"].virtual_type.metaclass, [t["Base"].metaclass, t["Foo"].metaclass, t["Bar"].metaclass]}
    end
  end

  it "VirtualMetaclassType with concrete base" do
    assert_concrete_types(%(
      class Base
      end

      class Foo < Base
      end

      class Bar < Base
      end
    )) do |t|
      {t["Base"].virtual_type.metaclass, [t["Base"].metaclass, t["Foo"].metaclass, t["Bar"].metaclass]}
    end
  end

  it "ModuleType" do
    assert_concrete_types(%(
      module M
      end

      class Foo
        include M
      end

      class Bar
        include M
      end
    )) do |t|
      {t["M"], [t["Foo"], t["Bar"]]}
    end
  end

  it "GenericModuleInstanceType" do
    assert_concrete_types(%(
      module M(T)
      end

      class A
      end

      class B
      end

      class Q
      end

      class Foo
        include M(A)
      end

      class Bar
        include M(A)
      end

      class Baz
        include M(B)
      end

      # Q is used to so the remove_indirection has a GenericModuleInstanceType
      alias Anchor = M(A) | Q
    )) do |t, p|
      {t["Anchor"].remove_indirection, [t["Q"], t["Foo"], t["Bar"]]}
    end
  end
end
