diff --git a/lib/stream_data_types.ex b/lib/stream_data_types.ex index 436af4b..6fdb702 100644 --- a/lib/stream_data_types.ex +++ b/lib/stream_data_types.ex @@ -113,4 +113,48 @@ defmodule StreamDataTypes do defp generate({:type, _, :float, _}) do float() end + + defp generate({:type, _line, :fun, [{:type, _, :any}, ret_type]}) do + ret_type = generate(ret_type) + + create_fun(ret_type) + end + + defp generate({:type, _line, :fun, [{:type, _, :product, args}, ret_type]}) do + arity = length(args) + ret_type = generate(ret_type) + + create_fun(arity, ret_type) + end + + @fn_arity_cap 20 + defp create_fun(arity \\ nil, %StreamData{generator: generator}) do + f = fn seed, size -> + arity = + if arity do + arity + else + {int, _next_seed} = :rand.uniform_s(@fn_arity_cap, seed) + int + end + + vars = Macro.generate_arguments(arity, Elixir) + + ast = quote do + fn unquote_splicing(vars) -> + int = :erlang.phash2([unquote_splicing(vars)]) + new_seed = :rand.seed_s(:exsp, {int, 0, 0}) + var!(generator).(new_seed, var!(size)).root + end + end + + {root, _} = Code.eval_quoted(ast, [generator: generator, size: size]) + lazy_tree(root) + end + %StreamData{generator: f} + end + + defp lazy_tree(root, children \\ []) do + %StreamData.LazyTree{root: root, children: children} + end end diff --git a/test/stream_data/types_list.ex b/test/stream_data/types_list.ex index 83a5091..fa48c61 100644 --- a/test/stream_data/types_list.ex +++ b/test/stream_data/types_list.ex @@ -7,4 +7,9 @@ defmodule StreamDataTest.TypesList do @type basic_neg_integer() :: neg_integer() @type basic_non_neg_integer() :: non_neg_integer() @type basic_pos_integer() :: pos_integer() + + @type literal_function_arity_any :: (... -> float()) + @type literal_function_arity_0 :: (() -> neg_integer()) + @type literal_function_arity_1 :: (any() -> integer()) + @type literal_function_arity_2 :: (integer(), atom() -> integer()) end diff --git a/test/stream_data/types_test.exs b/test/stream_data/types_test.exs index e5168d9..8c7baaf 100644 --- a/test/stream_data/types_test.exs +++ b/test/stream_data/types_test.exs @@ -78,6 +78,56 @@ defmodule StreamData.TypesTest do end end + describe "literals" do + test "function with any arity" do + data = generate_data(:literal_function_arity_any) + + check all f <- data, i <- integer() do + assert is_function(f) + + {:arity, arity} = :erlang.fun_info(f, :arity) + args = List.duplicate(i, arity) + + ast = quote do + var!(f).(unquote_splicing(args)) + end + {int, _} = Code.eval_quoted(ast, f: f) + + assert is_float(int) + end + end + + test "function literal with 0 arguments" do + data = generate_data(:literal_function_arity_0) + + check all f <- data do + assert is_function(f, 0) + assert f.() == f.() # The function is pure + int = f.() + assert is_integer(int) + assert int < 0 + end + end + + test "function literal with 1 argument" do + data = generate_data(:literal_function_arity_1) + + check all f <- data, i <- integer() do + assert is_function(f, 1) + assert is_integer(f.(i)) + end + end + + test "function literal with 2 arguments" do + data = generate_data(:literal_function_arity_2) + + check all f <- data, a <- integer(), b <- integer() do + assert is_function(f, 2) + assert is_integer(f.(a, b)) + end + end + end + defp generate_data(name, args \\ []) do Types.from_type(StreamDataTest.TypesList, name, args) end