Skip to content

Instantly share code, notes, and snippets.

@Qqwy
Last active September 23, 2021 14:43
Show Gist options
  • Save Qqwy/715b31cee2c93e7a658b4b4e51ea4b37 to your computer and use it in GitHub Desktop.
Save Qqwy/715b31cee2c93e7a658b4b4e51ea4b37 to your computer and use it in GitHub Desktop.
debug_example.ex
defmodule DebugExample do
@compile :inline
@compile {:inline_size, 100}
use TypeCheck, debug: true
@spec! stringify(integer()) :: binary()
def stringify(val) do
to_string(val)
end
end
TypeCheck.Macros @spec generated:
----------------
if(Module.get_attribute(__MODULE__, :autogen_typespec)) do
@spec stringify(integer()) :: binary()
end
defoverridable(stringify: 1)
def(stringify(arg1)) do
with(
{{:ok, _bindings}, _index, _param_type} <-
{case(arg1) do
x when is_integer(x) ->
{:ok, []}
_ ->
{:error, {%{__struct__: TypeCheck.Builtin.Integer}, :no_match, %{}, arg1}}
end, 0, %{__struct__: TypeCheck.Builtin.Integer}}
) do
else
{{:error, problem}, index, param_type} ->
raise(
TypeCheck.TypeError,
{{__MODULE__."__TypeCheck spec for 'stringify/1'__"(), :param_error,
%{index: index, problem: problem}, [arg1]}, [file: "iex", line: 1]}
)
end
var!(super_result, nil) = super(arg1)
case(
case(super_result) do
x when is_binary(x) ->
{:ok, []}
_ ->
{:error, {%{__struct__: TypeCheck.Builtin.Binary}, :no_match, %{}, super_result}}
end
) do
{:ok, _bindings} ->
nil
{:error, problem} ->
raise(
TypeCheck.TypeError,
{__MODULE__."__TypeCheck spec for 'stringify/1'__"(), :return_error,
%{problem: problem, arguments: [arg1]}, var!(super_result, nil)}
)
end
var!(super_result, nil)
end
@Qqwy
Copy link
Author

Qqwy commented Sep 23, 2021

For the curious, this results in the following BEAM bytecode:

   {:function, :stringify, 1, 11,
    [
      {:line, 1},
      {:label, 10},
      {:func_info, {:atom, DebugExample}, {:atom, :stringify}, 1},
      {:label, 11},
      {:test, :is_integer, {:f, 12}, [x: 0]},
      {:move, {:literal, {:ok, []}}, {:x, 1}},
      {:jump, {:f, 13}},
      {:label, 12},
      {:test_heap, 8, 1},
      {:put_tuple2, {:x, 1},
       {:list,
        [
          literal: #TypeCheck.Type< integer() >,
          atom: :no_match,
          literal: %{},
          x: 0
        ]}},
      {:put_tuple2, {:x, 1}, {:list, [atom: :error, x: 1]}},
      {:label, 13},
      {:test, :is_tagged_tuple, {:f, 19}, [{:x, 1}, 2, {:atom, :ok}]},
      {:allocate, 3, 1},
      {:init_yregs, {:list, [y: 0, y: 1]}},
      {:move, {:x, 0}, {:y, 2}},
      {:test, :is_binary, {:f, 14}, [x: 0]},
      {:move, {:y, 2}, {:y, 1}},
      {:jump, {:f, 15}},
      {:label, 14},
      {:line, 2},
      {:call_ext, 1, {:extfunc, String.Chars, :to_string, 1}},
      {:move, {:x, 0}, {:y, 1}},
      {:label, 15},
      {:test, :is_binary, {:f, 16}, [y: 1]},
      {:move, {:literal, {:ok, []}}, {:x, 0}},
      {:jump, {:f, 17}},
      {:label, 16},
      {:test_heap, 8, 0},
      {:put_tuple2, {:x, 0},
       {:list,
        [
          literal: #TypeCheck.Type< binary() >,
          atom: :no_match,
          literal: %{},
          y: 1
        ]}},
      {:put_tuple2, {:x, 0}, {:list, [atom: :error, x: 0]}},
      {:label, 17},
      {:get_tuple_element, {:x, 0}, 0, {:x, 1}},
      {:get_tuple_element, {:x, 0}, 1, {:y, 0}},
      {:test, :is_eq_exact, {:f, 18}, [x: 1, atom: :ok]},
      {:move, {:y, 1}, {:x, 0}},
      {:deallocate, 3},
      :return,
      {:label, 18},
      {:line, 3},
      {:call_ext, 0,
       {:extfunc, DebugExample, :"__TypeCheck spec for 'stringify/1'__", 0}},
      {:test_heap, 2, 1},
      {:put_list, {:y, 2}, nil, {:x, 1}},
      {:put_map_assoc, {:f, 0}, {:literal, %{}}, {:x, 1}, 2,
       {:list, [atom: :arguments, x: 1, atom: :problem, y: 0]}},
      {:test_heap, 5, 2},
      {:put_tuple2, {:x, 0}, {:list, [x: 0, atom: :return_error, x: 1, y: 1]}},
      {:trim, 3, 0},
      {:line, 4},
      {:call_ext, 1, {:extfunc, TypeCheck.TypeError, :exception, 1}},
      {:call_ext_last, 1, {:extfunc, :erlang, :error, 1}, 0},
      {:label, 19},
      {:allocate, 2, 2},
      {:move, {:x, 1}, {:y, 0}},
      {:move, {:x, 0}, {:y, 1}},
      {:line, 5},
      {:call_ext, 0,
       {:extfunc, DebugExample, :"__TypeCheck spec for 'stringify/1'__", 0}},
      {:get_tuple_element, {:y, 0}, 1, {:x, 1}},
      {:put_map_assoc, {:f, 0}, {:literal, %{index: 0}}, {:x, 1}, 2,
       {:list, [atom: :problem, x: 1]}},
      {:test_heap, 10, 2},
      {:put_list, {:y, 1}, nil, {:x, 2}},
      {:put_tuple2, {:x, 0}, {:list, [x: 0, atom: :param_error, x: 1, x: 2]}},
      {:put_tuple2, {:x, 0},
       {:list,
        [
          x: 0,
          literal: [
            file: "[...]/debug_example.ex",
            line: 1
          ]
        ]}},
      {:trim, 2, 0},
      {:line, 6},
      {:call_ext, 1, {:extfunc, TypeCheck.TypeError, :exception, 1}},
      {:call_ext_last, 1, {:extfunc, :erlang, :error, 1}, 0}
    ]}

Which is roughly equivalent to this Elixir code:

def stringify(val) do
  # Check parameters
  params_check =
    if is_integer(val) do
      {:ok, []}
    else
      {:error, %TypeCheck.Builtin.Integer{}, :no_match, %{}, val}
    end

  # If there was a problem with any of the parameters, raise now.
  # Note that since our example only has a single parameter,
  # the compiler has optimized the 'with'-statement away.
  case params_check do
    {:error, type, inner_problem, _bindings, val} ->
      problem = {DebugExample."__TypeCheck spec for 'stringify/1'__"(), :param_error, %{index: 0, problem: inner_problem}, [val]}
      raise TypeCheck.TypeError, problem
    {:ok, _} ->

      # Call the original implementation
      # (This is inlined if you use `@compile :inline` in your module)
      implementation_result = to_string(val)

      # Check return type
      return_check =
        if is_binary(res) do
            {:ok, []}
        else
            {:error, %TypeCheck.Builtin.Binary{}, :no_match, %{}, res}
        end

      # If there was a problem with the return type, raise now
      case return_check do
        {:ok, _} ->
          implementation_result
        {:error, type, inner_problem, _, _} ->
          problem = {DebugExample."__TypeCheck spec for 'stringify/1'__"(), :return_error, %{problem: inner_problem, arguments: [val]}, implementation_result}
          raise TypeCheck.TypeError, problem
      end
  end
end

So as you can see, when the more advanced features of TypeCheck, such as using named types / type guards are not used, these things are optimized away by the compiler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment