 
   test "type_to_ast for paren_type" do
     type = {:my_type, {:paren_type, 0, [{:type, 0, :integer, []}]}, []}
+
     assert Kernel.Typespec.type_to_ast(type) ==
-      {:::, [], [{:my_type, [], []}, {:integer, [line: 0], []}]}
+             {:::, [], [{:my_type, [], []}, {:integer, [line: 0], []}]}
   end
 
   test "spec_to_ast" do
-    quoted = [
-      quote(do: @spec foo() :: integer()),
-      quote(do: @spec foo(atom()) :: integer() | [{}]),
-      quote(do: @spec foo(arg) :: integer() when [arg: integer()]),
-      quote(do: @spec foo(arg) :: arg when [arg: var]),
-      quote(do: @spec foo(arg :: atom()) :: atom()),
-    ]
-    |> Enum.sort
-
-    bytecode = test_module do
-      def foo(), do: 1
-      def foo(arg), do: arg
-      Module.eval_quoted __MODULE__, quote(do: (unquote_splicing(quoted)))
-    end
-
-    specs = Enum.flat_map(specs(bytecode), fn {{_, _}, specs} ->
-       Enum.map(specs, fn spec ->
-         quote(do: @spec unquote(Kernel.Typespec.spec_to_ast(:foo, spec)))
-       end)
-    end)
-    |> Enum.sort
-
-    Enum.each(Enum.zip(specs, quoted), fn {spec, definition} ->
+    quoted =
+      Enum.sort([
+        quote(do: @spec(foo() :: integer())),
+        quote(do: @spec(foo(atom()) :: integer() | [{}])),
+        quote(do: @spec(foo(arg) :: integer() when [arg: integer()])),
+        quote(do: @spec(foo(arg) :: arg when [arg: var])),
+        quote(do: @spec(foo(arg :: atom()) :: atom()))
+      ])
+
+    bytecode =
+      test_module do
+        def foo(), do: 1
+        def foo(arg), do: arg
+        Module.eval_quoted(__MODULE__, quote(do: (unquote_splicing(quoted))))
+      end
+
+    specs =
+      Enum.flat_map(specs(bytecode), fn {{_, _}, specs} ->
+        Enum.map(specs, fn spec ->
+          quote(do: @spec(unquote(Kernel.Typespec.spec_to_ast(:foo, spec))))
+        end)
+      end)
+
+    specs_with_quoted = specs |> Enum.sort() |> Enum.zip(quoted)
+
+    Enum.each(specs_with_quoted, fn {spec, definition} ->
       assert Macro.to_string(spec) == Macro.to_string(definition)
     end)
   end

  defmodule SampleCallbacks do
    @callback first(integer) :: integer
    @callback foo(atom(), binary) :: binary
    @callback bar(External.hello, my_var :: binary) :: binary
    @callback guarded(my_var) :: my_var when my_var: binary
    @callback orr(atom | integer) :: atom
    @callback literal(123, {atom}, :foo, [integer], true) :: atom
    @macrocallback last(integer) :: Macro.t
    @macrocallback last() :: atom
    @optional_callbacks bar: 2, last: 0
    @optional_callbacks first: 1
  end

  test "callbacks" do
    assert SampleCallbacks.behaviour_info(:callbacks) |> Enum.sort() ==
           ["MACRO-last": 1, "MACRO-last": 2, bar: 2, first: 1, foo: 2, guarded: 1, literal: 5, orr: 1]
  end

  test "optional callbacks" do
    assert SampleCallbacks.behaviour_info(:optional_callbacks) |> Enum.sort() ==
           ["MACRO-last": 1, bar: 2, first: 1]
  end

  test "default is not supported" do

  end

  test "@spec(spec)" do
    bytecode =
      test_module do
        def my_fun1(x), do: x
        def my_fun2(), do: :ok
        def my_fun3(x, y), do: {x, y}
        def my_fun4(x), do: x
        @spec my_fun1(integer) :: integer
        @spec my_fun2() :: integer
        @spec my_fun3(integer, integer) :: {integer, integer}
        @spec my_fun4(x :: integer) :: integer
      end

    assert [my_fun1, my_fun2, my_fun3, my_fun4] = specs(bytecode)

    assert {{:my_fun1, 1}, [{:type, _, :fun, args}]} = my_fun1
    assert [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}] = args

    assert {{:my_fun2, 0}, [{:type, _, :fun, args}]} = my_fun2
    assert [{:type, _, :product, []}, {:type, _, :integer, []}] = args

    assert {{:my_fun3, 2}, [{:type, _, :fun, [arg1, arg2]}]} = my_fun3
    assert {:type, _, :product, [{:type, _, :integer, []}, {:type, _, :integer, []}]} = arg1
    assert {:type, _, :tuple, [{:type, _, :integer, []}, {:type, _, :integer, []}]} = arg2

    assert {{:my_fun4, 1}, [{:type, _, :fun, args}]} = my_fun4
    assert [x, {:type, _, :integer, []}] = args
    assert {:type, _, :product, [{:ann_type, _, [{:var, _, :x}, {:type, _, :integer, []}]}]} = x
  end

  test "@spec(spec) with tuples and tuple vars" do
    bytecode =
      test_module do
        def my_fun1(x), do: x
        def my_fun2(x), do: x
        @spec my_fun1(tuple) :: tuple
        @spec my_fun2(tuple) :: tuple when tuple: {integer, integer}
      end

    assert [my_fun1, my_fun2] = specs(bytecode)

    assert {{:my_fun1, 1}, [{:type, _, :fun, args}]} = my_fun1
    assert [{:type, _, :product, [{:type, _, :tuple, :any}]}, {:type, _, :tuple, :any}] = args

    assert {{:my_fun2, 1}, [{:type, _, :bounded_fun, args}]} = my_fun2

    assert [type, _] = args
    assert {:type, _, :fun, [{:type, _, :product, [{:var, _, :tuple}]}, {:var, _, :tuple}]} = type
  end

  test "@spec(spec) for unreachable private function" do
    # Use capture_io/2 to capture the "my_fun/1 is unused" warning.
    output =
      ExUnit.CaptureIO.capture_io(:stderr, fn ->
        bytecode =
          test_module do
            defp my_fun(x), do: x
            @spec my_fun(integer) :: integer
          end

        assert specs(bytecode) == []
      end)

    assert output != ""
  end

  test "@spec(spec) with guards" do
    bytecode =
      test_module do
        def my_fun1(x), do: x
        @spec my_fun1(x) :: boolean when x: integer

        def my_fun2(x), do: x
        @spec my_fun2(x) :: x when x: var

        def my_fun3(_x, y), do: y
        @spec my_fun3(x, y) :: y when y: x, x: var
      end

    assert [my_fun1, my_fun2, my_fun3] = specs(bytecode)

    assert {{:my_fun1, 1}, [{:type, _, :bounded_fun, args}]} = my_fun1
    assert [{:type, _, :fun, [product, {:type, _, :boolean, []}]}, constraints] = args
    assert {:type, _, :product, [{:var, _, :x}]} = product
    assert [{:type, _, :constraint, subtype}] = constraints
    assert [{:atom, _, :is_subtype}, [{:var, _, :x}, {:type, _, :integer, []}]] = subtype

    assert {{:my_fun2, 1}, [{:type, _, :fun, args}]} = my_fun2
    assert [{:type, _, :product, [{:var, _, :x}]}, {:var, _, :x}] = args

    assert {{:my_fun3, 2}, [{:type, _, :bounded_fun, args}]} = my_fun3
    assert [{:type, _, :fun, fun_type}, [{:type, _, :constraint, constraint_type}]] = args
    assert [{:type, _, :product, [{:var, _, :x}, {:var, _, :y}]}, {:var, _, :y}] = fun_type
    assert [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]] = constraint_type
  end

  test "@callback(callback)" do
    bytecode =
      test_module do
        @callback my_fun(integer) :: integer
        @callback my_fun(list) :: list
        @callback my_fun() :: integer
        @callback my_fun(integer, integer) :: {integer, integer}
      end

    assert [my_fun_0, my_fun_1, my_fun_2] = callbacks(bytecode)

    assert {{:my_fun, 0}, [{:type, _, :fun, args}]} = my_fun_0
    assert [{:type, _, :product, []}, {:type, _, :integer, []}] = args

    assert {{:my_fun, 1}, [clause1, clause2]} = my_fun_1
    assert {:type, _, :fun, args1} = clause1
    assert [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}] = args1
    assert {:type, _, :fun, args2} = clause2
    assert [{:type, _, :product, [{:type, _, :list, []}]}, {:type, _, :list, []}] = args2

    assert {{:my_fun, 2}, [{:type, _, :fun, [args_type, return_type]}]} = my_fun_2
    assert {:type, _, :product, [{:type, _, :integer, []}, {:type, _, :integer, []}]} = args_type
    assert {:type, _, :tuple, [{:type, _, :integer, []}, {:type, _, :integer, []}]} = return_type
  end

  test "@spec + @callback" do
    bytecode =
      test_module do
        def my_fun(x), do: x
        @spec my_fun(integer) :: integer
        @spec my_fun(charlist) :: charlist
        @callback cb(integer) :: integer
      end

    assert [{{:cb, 1}, [{:type, _, :fun, args}]}] = callbacks(bytecode)
    assert [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}] = args

    assert [{{:my_fun, 1}, [integer_clause, charlist_clause]}] = specs(bytecode)

    assert {:type, _, :fun, [{:type, _, :product, [arg]}, return]} = integer_clause
    assert {:type, _, :integer, []} = arg
    assert {:type, _, :integer, []} = return

    assert {:type, _, :fun, [{:type, _, :product, [arg]}, return]} = charlist_clause
    assert {:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :charlist}, []]} = arg
    assert {:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :charlist}, []]} = return
  end

  test "block handling" do
    bytecode =
      test_module do
        @spec foo((() -> [integer])) :: integer
        def foo(_), do: 1
      end

    assert [{{:foo, 1}, [{:type, _, :fun, [args, return]}]}] = specs(bytecode)
    assert {:type, _, :product, [{:type, _, :fun, fun_args}]} = args
    assert [{:type, _, :product, []}, {:type, _, :list, [{:type, _, :integer, []}]}] = fun_args
    assert {:type, _, :integer, []} = return
  end

  # Conversion to AST

  test "type_to_ast" do
    quoted =
      Enum.sort([
        quote(do: @type(with_ann() :: t :: atom())),
        quote(do: @type(a_tuple() :: tuple())),
        quote(do: @type(empty_tuple() :: {})),
        quote(do: @type(one_tuple() :: {:foo})),
        quote(do: @type(two_tuple() :: {:foo, :bar})),
        quote(do: @type(imm_type_1() :: 1)),
        quote(do: @type(imm_type_2() :: :foo)),
        quote(do: @type(simple_type() :: integer())),
        quote(do: @type(param_type(p) :: [p])),
        quote(do: @type(union_type() :: integer() | binary() | boolean())),
        quote(do: @type(binary_type1() :: <<_::_*8>>)),
        quote(do: @type(binary_type2() :: <<_::3>>)),
        quote(do: @type(binary_type3() :: <<_::3, _::_*8>>)),
        quote(do: @type(tuple_type() :: {integer()})),
        quote(do: @type(ftype() :: (() -> any()) | (() -> integer()) | (integer() -> integer()))),
        quote(do: @type(cl() :: charlist())),
        quote(do: @type(st() :: struct())),
        quote(do: @type(ab() :: as_boolean(term()))),
        quote(do: @type(kw() :: keyword())),
        quote(do: @type(kwt() :: keyword(term()))),
        quote(do: @type(vaf() :: (... -> any()))),
        quote(do: @type(rng() :: 1..10)),
        quote(do: @type(opts() :: [first: integer(), step: integer(), last: integer()])),
        quote(do: @type(ops() :: {+1, -1})),
        quote(do: @type(a_map() :: map())),
        quote(do: @type(empty_map() :: %{})),
        quote(do: @type(my_map() :: %{hello: :world})),
        quote(do: @type(my_req_map() :: %{required(0) => :foo})),
        quote(do: @type(my_opt_map() :: %{optional(0) => :foo})),
        quote(do: @type(my_struct() :: %Kernel.TypespecTest{hello: :world})),
        quote(do: @type(list1() :: list())),
        quote(do: @type(list2() :: [0])),
        quote(do: @type(list3() :: [...])),
        quote(do: @type(list4() :: [0, ...])),
        quote(do: @type(nil_list() :: []))
      ])

    bytecode =
      test_module do
        Module.eval_quoted(__MODULE__, quoted)
      end

    types = types(bytecode)

    Enum.each(Enum.zip(types, quoted), fn {{:type, type}, definition} ->
      ast = Kernel.Typespec.type_to_ast(type)
      assert Macro.to_string(quote(do: @type(unquote(ast)))) == Macro.to_string(definition)
    end)
  end

