Skip to content

Commit

Permalink
Fix type to string representation for complex types
Browse files Browse the repository at this point in the history
  • Loading branch information
edgurgel committed Aug 29, 2024
1 parent d82da2f commit edb1b56
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 23 deletions.
25 changes: 2 additions & 23 deletions lib/ham/type_match_error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ defmodule Ham.TypeMatchError do
match types defined in typespecs.
"""
defexception [:reasons]

@type t :: %__MODULE__{}

alias Ham.Utils
import Ham.Utils, only: [type_to_string: 1]

@impl Exception
def exception({:error, reasons}), do: %__MODULE__{reasons: reasons}
Expand Down Expand Up @@ -123,27 +125,4 @@ defmodule Ham.TypeMatchError do

padding <> string
end

defp type_to_string({:type, _, :map_field_exact, [type1, type2]}) do
"required(#{type_to_string(type1)}) => #{type_to_string(type2)}"
end

defp type_to_string({:type, _, :map_field_assoc, [type1, type2]}) do
"optional(#{type_to_string(type1)}) => #{type_to_string(type2)}"
end

defp type_to_string(type) do
# We really want to access Code.Typespec.typespec_to_quoted/1 here but it's
# private... this hack needs to suffice.
{:foo, type, []}
|> Code.Typespec.type_to_quoted()
|> Macro.to_string()
|> String.split("\n")
|> Enum.map_join(&String.replace(&1, ~r/ +/, " "))
|> String.split(" :: ")
|> case do
[_, type_string] -> type_string
[_, type_name, type_string] -> "#{type_string} (\"#{type_name}\")"
end
end
end
34 changes: 34 additions & 0 deletions lib/ham/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,38 @@ defmodule Ham.Utils do
defp suffix(2), do: "nd"
defp suffix(3), do: "rd"
defp suffix(_), do: "th"

def type_to_string({:type, _, :map_field_exact, [type1, type2]}) do
"required(#{type_to_string(type1)}) => #{type_to_string(type2)}"
end

def type_to_string({:type, _, :map_field_assoc, [type1, type2]}) do
"optional(#{type_to_string(type1)}) => #{type_to_string(type2)}"
end

def type_to_string(type) do
# We really want to access Code.Typespec.typespec_to_quoted/1 here but it's
# private... this hack needs to suffice.
macro =
{:foo, type, []}
|> Code.Typespec.type_to_quoted()

macro
|> Macro.to_string()
|> String.split("\n")
|> Enum.map_join(&String.replace(&1, ~r/ +/, " "))
|> String.split(" :: ", parts: type_parts(macro))
|> case do
[_foo, type_string] -> type_string
[_foo, type_name, type_string] -> "#{type_string} (\"#{type_name}\")"
end
end

defp type_parts(macro) do
case macro do
# The type has a name
{:"::", [], [{:foo, [], []}, {:"::", _, _}]} -> 3
_ -> 2
end
end
end
15 changes: 15 additions & 0 deletions test/ham/type_checker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,21 @@ defmodule Ham.TypeCheckerTest do
end
end

describe "multiple options" do
test "pass" do
assert_pass(:foo_multiple_options_annotated, [], {:ok, "a", "b"})
end

test "fail" do
assert_fail(
:foo_multiple_options_annotated,
[],
:ok,
~r/type \{:ok, binary\(\), req :: binary\(\)\} | \{:more, binary\(\), req :: binary\(\)\} \("result"\)/
)
end
end

test "nospec" do
assert_pass(:nospec_fun, [], :ok)
end
Expand Down
26 changes: 26 additions & 0 deletions test/ham/utils_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Ham.UtilsTest do
use ExUnit.Case, async: true
alias Ham.Utils

defp fetch_test_type(type_name) do
{:ok, types} =
Code.Typespec.fetch_types(Ham.Test.TestModule)

Enum.find_value(types, fn
{:type, {^type_name, type, _}} -> type
_ -> nil
end)
end

describe "type_to_string/1" do
test "complex type" do
assert :complex |> fetch_test_type() |> Utils.type_to_string() ==
"{:ok, binary(), req :: binary()} | {:more, binary(), req :: binary()}"
end

test "complex type with annotation" do
assert :complex_annotated |> fetch_test_type() |> Utils.type_to_string() ==
"{:ok, binary(), req :: binary()} | {:more, binary(), req :: binary()} (\"result\")"
end
end
end
13 changes: 13 additions & 0 deletions test/support/module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,17 @@ defmodule Ham.Test.TestModule do
def foo_guarded(_arg), do: [1]

def nospec_fun, do: :ok

@spec map_type_with_underscore() :: :test.req()
def map_type_with_underscore, do: %{method: "GET"}

@type complex :: {:ok, binary(), req :: binary()} | {:more, binary(), req :: binary()}
@type complex_annotated ::
result :: {:ok, binary(), req :: binary()} | {:more, binary(), req :: binary()}

@spec foo_multiple_options_annotated :: complex_annotated
def foo_multiple_options_annotated, do: {:ok, "a", "b"}

@spec foo_multiple_options :: complex
def foo_multiple_options, do: {:ok, "a", "b"}
end

0 comments on commit edb1b56

Please sign in to comment.