From b4cb211999962fdbb8bcce71914736b91f232d2d Mon Sep 17 00:00:00 2001 From: isaac yonemoito Date: Fri, 26 Apr 2024 10:04:48 -0500 Subject: [PATCH] makes more tests pass, up to marshalling --- lib/zig/sema.ex | 4 +- lib/zig/templates/basic.zig.eex | 2 +- lib/zig/type.ex | 23 +- lib/zig/type/array.ex | 9 +- lib/zig/type/bool.ex | 6 + lib/zig/type/cpointer.ex | 6 + lib/zig/type/enum.ex | 9 +- lib/zig/type/float.ex | 4 + lib/zig/type/integer.ex | 12 +- lib/zig/type/manypointer.ex | 5 + lib/zig/type/optional.ex | 6 + lib/zig/type/slice.ex | 4 + lib/zig/type/struct.ex | 5 + test/integration/types/c_pointer_test.exs | 314 +++++++++++----------- test/integration/types/enum_test.exs | 83 +++--- test/integration/types/struct_test.exs | 235 ++++++++-------- test/integration/types/void_test.exs | 25 +- 17 files changed, 404 insertions(+), 348 deletions(-) diff --git a/lib/zig/sema.ex b/lib/zig/sema.ex index d2526632..9c1dd3ac 100644 --- a/lib/zig/sema.ex +++ b/lib/zig/sema.ex @@ -52,8 +52,8 @@ defmodule Zig.Sema do defp integrate_sema(%{"functions" => functions, "types" => types, "decls" => decls}, module) do %__MODULE__{ - functions: Enum.map(functions, &Function.from_json(&1, module)), - types: Enum.map(types, &type_from_json(&1, module)), + functions: Enum.map(functions, &Function.from_json(&1, module.module)), + types: Enum.map(types, &type_from_json(&1, module.module)), decls: Enum.map(decls, &const_from_json/1) } end diff --git a/lib/zig/templates/basic.zig.eex b/lib/zig/templates/basic.zig.eex index 13d57483..77a98bed 100644 --- a/lib/zig/templates/basic.zig.eex +++ b/lib/zig/templates/basic.zig.eex @@ -15,7 +15,7 @@ fn <%= @name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callc return result_block: { const payload_opts = .{ <%= for {index, param} <- @params do %> - <%= Type.render_payload_entry(param, index, needs_make?) %> + <%= Type.render_payload_options(param, index, needs_make?) %> <% end %> }; diff --git a/lib/zig/type.ex b/lib/zig/type.ex index 8403553e..4c767157 100644 --- a/lib/zig/type.ex +++ b/lib/zig/type.ex @@ -36,12 +36,15 @@ defprotocol Zig.Type do def return_allowed?(type) # rendered zig code: - @spec render_payload_entry(t, non_neg_integer, boolean) :: iodata - def render_payload_entry(type, index, error_info?) + @spec render_payload_options(t, non_neg_integer, boolean) :: iodata + def render_payload_options(type, index, error_info?) @spec render_return(t) :: iodata def render_return(type) + @spec render_zig(t) :: String.T + def render_zig(type) + @typep spec_context :: :param | :return @spec spec(t, spec_context, keyword) :: Macro.t() def spec(type, context, opts) @@ -225,11 +228,13 @@ after # defaults - def _default_payload_entry, do: ".{.error_info = &error_info}," + def _default_payload_options, do: ".{.error_info = &error_info}," def _default_return, do: "break :result_block beam.make(result, .{}).v;" end defimpl Zig.Type, for: Atom do + alias Zig.Type + def marshals_param?(_), do: false def marshals_return?(_), do: false @@ -238,7 +243,17 @@ defimpl Zig.Type, for: Atom do def return_allowed?(type), do: type in ~w(term erl_nif_term pid void)a - def render_return(:void), do: "" + def render_return(:void), do: "_ = result; break :result_block beam.make(.ok, .{}).v;" + def render_return(_), do: Type._default_return() + + def render_payload_options(:erl_nif_term, _, _), do: ".{}" + def render_payload_options(:term, _, _), do: ".{}" + + def render_payload_options(type, _, _) + when type in ~w[env stacktrace erl_nif_binary erl_nif_event erl_nif_binary_pointer]a, + do: raise("unreachable") + + def render_payload_options(_, _, _), do: Type._default_payload_options() def spec(:void, :return, _), do: :ok diff --git a/lib/zig/type/array.ex b/lib/zig/type/array.ex index bc51f9ec..14a97131 100644 --- a/lib/zig/type/array.ex +++ b/lib/zig/type/array.ex @@ -26,10 +26,11 @@ defmodule Zig.Type.Array do } end + def return_allowed?(array), do: Type.return_allowed?(array.child) def marshals_param?(_), do: false def marshals_return?(_), do: false - - def return_allowed?(array), do: Type.return_allowed?(array.child) + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() def spec(%{child: ~t(u8)} = type, :return, opts) do # u8 defaults to binary @@ -113,8 +114,4 @@ defmodule Zig.Type.Array do def of(type, len, opts \\ []) do struct(__MODULE__, opts ++ [child: type, len: len]) end - - def render_payload_entry(type, index, _), do: Type._default_payload_entry() - - def render_return(type), do: Type._default_return() end diff --git a/lib/zig/type/bool.ex b/lib/zig/type/bool.ex index 281f42b8..1e91ae55 100644 --- a/lib/zig/type/bool.ex +++ b/lib/zig/type/bool.ex @@ -10,5 +10,11 @@ defmodule Zig.Type.Bool do def return_allowed?(_), do: true + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() + + def marshals_param?(_), do: false + def marshals_return?(_), do: false + def spec(_, _, _), do: Type.spec(:boolean) end diff --git a/lib/zig/type/cpointer.ex b/lib/zig/type/cpointer.ex index 0d1832b3..ef91434d 100644 --- a/lib/zig/type/cpointer.ex +++ b/lib/zig/type/cpointer.ex @@ -24,6 +24,12 @@ defmodule Zig.Type.Cpointer do end end + def marshals_param?(_), do: false + def marshals_return?(_), do: false + + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() + def spec(%{child: child}, :param, _opts) do has_solo? = match?(%Type.Struct{extern: true}, child) child_form = Type.spec(child, :param, []) diff --git a/lib/zig/type/enum.ex b/lib/zig/type/enum.ex index b0ed93ce..4467fa78 100644 --- a/lib/zig/type/enum.ex +++ b/lib/zig/type/enum.ex @@ -1,5 +1,6 @@ defmodule Zig.Type.Enum do - use Zig.Type + alias Zig.Type + use Type defstruct [:tags, :name] @type t :: %__MODULE__{tags: %{optional(atom) => String.t()}, name: String.t()} @@ -17,6 +18,12 @@ defmodule Zig.Type.Enum do def return_allowed?(_), do: true + def marshals_param?(_), do: false + def marshals_return?(_), do: false + + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() + def spec(%{tags: tags}, _, _opts) do tags |> Map.keys() diff --git a/lib/zig/type/float.ex b/lib/zig/type/float.ex index 04d2a542..0cce2116 100644 --- a/lib/zig/type/float.ex +++ b/lib/zig/type/float.ex @@ -22,4 +22,8 @@ defmodule Zig.Type.Float do def spec(_, _, _), do: Type.spec(:float) def return_allowed?(_), do: true + def marshals_param?(_), do: false + def marshals_return?(_), do: false + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() end diff --git a/lib/zig/type/integer.ex b/lib/zig/type/integer.ex index feb1f177..90496276 100644 --- a/lib/zig/type/integer.ex +++ b/lib/zig/type/integer.ex @@ -20,6 +20,10 @@ defmodule Zig.Type.Integer do def parse(other), do: raise(Zig.Type.ParseError, source: other) + def render_zig(%{bits: :big}), do: raise("not supported yet") + def render_zig(%{signedness: :unsigned, bits: bits}), do: "u#{bits}" + def render_zig(%{signedness: :signed, bits: bits}), do: "i#{bits}" + @signs %{"signed" => :signed, "unsigned" => :unsigned} def from_json(%{"signedness" => s, "bits" => b}) do %__MODULE__{signedness: Map.fetch!(@signs, s), bits: b} @@ -65,7 +69,7 @@ defmodule Zig.Type.Integer do guards = quote bind_quoted: [ variable: variable, - name: to_string(type), + name: Type.render_zig(type), index: index, max: max, min: min @@ -219,4 +223,10 @@ defmodule Zig.Type.Integer do def return_allowed?(_), do: true def render_return(type), do: Type._default_return() + + def marshals_param?(%{bits: bits}), do: bits > 64 + def marshals_return?(%{bits: bits}), do: bits > 64 + + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() end diff --git a/lib/zig/type/manypointer.ex b/lib/zig/type/manypointer.ex index 364fd28a..4880c5f9 100644 --- a/lib/zig/type/manypointer.ex +++ b/lib/zig/type/manypointer.ex @@ -26,6 +26,11 @@ defmodule Zig.Type.Manypointer do def return_allowed?(pointer), do: pointer.has_sentinel? and Type.return_allowed?(pointer.child) def missing_size?(_), do: true + def marshals_param?(_), do: false + def marshals_return?(_), do: false + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() + # only manypointers of [*:0]u8 are allowed to be returned. def spec(%{child: ~t(u8), has_sentinel?: true}, :return, opts) do case Keyword.fetch!(opts, :type) do diff --git a/lib/zig/type/optional.ex b/lib/zig/type/optional.ex index ad31c99c..4b00fd0e 100644 --- a/lib/zig/type/optional.ex +++ b/lib/zig/type/optional.ex @@ -11,6 +11,12 @@ defmodule Zig.Type.Optional do def return_allowed?(optional), do: Type.return_allowed?(optional.child) + def marshals_param?(_), do: false + def marshals_return?(_), do: false + + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() + def spec(%{child: child}, context, opts) do quote do unquote(Type.spec(child, context, opts)) | nil diff --git a/lib/zig/type/slice.ex b/lib/zig/type/slice.ex index 46ca91e3..271a7f6a 100644 --- a/lib/zig/type/slice.ex +++ b/lib/zig/type/slice.ex @@ -81,6 +81,10 @@ defmodule Zig.Type.Slice do # ETC def return_allowed?(slice), do: Type.return_allowed?(slice.child) + def marshals_param?(_), do: false + def marshals_return?(_), do: false + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() def of(child, opts \\ []), do: struct(__MODULE__, [child: child] ++ opts) end diff --git a/lib/zig/type/struct.ex b/lib/zig/type/struct.ex index 59af0df9..67c39311 100644 --- a/lib/zig/type/struct.ex +++ b/lib/zig/type/struct.ex @@ -118,4 +118,9 @@ defmodule Zig.Type.Struct do |> Enum.map(&Type.return_allowed?/1) |> Enum.all?() end + + def marshals_param?(_), do: false + def marshals_return?(_), do: false + def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_return(type), do: Type._default_return() end diff --git a/test/integration/types/c_pointer_test.exs b/test/integration/types/c_pointer_test.exs index 8e991bc9..dbaee095 100644 --- a/test/integration/types/c_pointer_test.exs +++ b/test/integration/types/c_pointer_test.exs @@ -4,161 +4,161 @@ defmodule ZiglerTest.Types.CPointerTest do @tag :skip test "restore leak check" - # use Zig, - # otp_app: :zigler, - # leak_check: false, - # nifs: [ - # {:cpointer_u8_list_return_test, return: :list}, - # ... - # ] - # - ### C pointers as single pointers - ### C pointers can be a single pointer to a struct, which makes it a "mutable" struct. - # ~Z""" - # pub const TestStruct = extern struct { value: i32 }; - # - # pub fn cpointer_test(passed: [*c]TestStruct) ?i32 { - # if (passed) |unwrapped| { - # return unwrapped.*.value; - # } else { - # return null; - # } - # } - # """ - # - # describe "for a struct cpointer" do - # test "you can pass a map" do - # assert 47 = cpointer_test(%{value: 47}) - # end - # - # test "you can pass null" do - # assert is_nil(cpointer_test(nil)) - # end - # - # test "you can't pass a keyword list" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | list(map | keyword) (for `[*c]TestStruct`)\n got: `[value: 47]`\n at index 0:\n | expected: map | keyword (for `TestStruct`)\n | got: `{:value, 47}`\n", - # fn -> - # cpointer_test(value: 47) - # end - # end - # - # test "you can't pass some other term" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | list(map | keyword) (for `[*c]TestStruct`)\n got: `:foo`\n note: [*c]TestStruct can take the atom `nil` but no other atom\n", - # fn -> - # cpointer_test(:foo) - # end - # end - # - # test "you can't pass a bad value term" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | list(map | keyword) (for `[*c]TestStruct`)\n got: `%{value: :foo}`\n in field `:value`:\n | expected: integer (for `i32`)\n | got: `:foo`\n", - # fn -> - # cpointer_test(%{value: :foo}) - # end - # end - # end - # - # ~Z""" - # pub fn cpointer_list_test(list: [*c]u8) ?u32 { - # var sum: u32 = 0; - # if (list) |_| { - # for (list[0..3]) |item| {sum += item;} - # return sum; - # } else return null; - # } - # - # pub fn cpointer_struct_list_test(list: [*c]TestStruct) ?i32 { - # var sum: i32 = 0; - # for (list[0..3]) |item| {sum += item.value;} - # return sum; - # } - # """ - # - # describe "for a list cpointer" do - # test "you can pass a list" do - # assert 6 == cpointer_list_test([1, 2, 3]) - # end - # - # test "you can pass null" do - # assert is_nil(cpointer_list_test(nil)) - # end - # - # test "you can pass a string for u8" do - # assert Enum.sum(~C"abc") == cpointer_list_test("abc") - # end - # - # test "you can pass a list of structs" do - # assert 6 = cpointer_struct_list_test([%{value: 1}, %{value: 2}, %{value: 3}]) - # end - # - # test "you can't pass a non-list term" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: binary | list(integer) (for `[*c]u8`)\n got: `:foo`\n note: [*c]u8 can take the atom `nil` but no other atom\n", - # fn -> - # cpointer_list_test(:foo) - # end - # end - # - # test "list item should be correctly typed" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: binary | list(integer) (for `[*c]u8`)\n got: `[:foo]`\n at index 0:\n | expected: integer (for `u8`)\n | got: `:foo`\n", - # fn -> - # cpointer_list_test([:foo]) - # end - # end - # end - # - # ~Z""" - # var u8_array_list = [_]u8{'a', 'b', 'c', 0}; - # pub fn cpointer_u8_return_test() [*c]u8 { - # return &u8_array_list; - # } - # - # pub fn cpointer_u8_list_return_test() [*c]u8 { - # return &u8_array_list; - # } - # - # var result_struct = TestStruct{.value = 47}; - # - # pub fn cpointer_struct_return_test() [*c]TestStruct { - # return &result_struct; - # } - # - # var struct_list: [2][*c]TestStruct = .{&result_struct, null}; - # - # pub fn cpointer_struct_list_return_test() [*c][*c]TestStruct { - # return &struct_list; - # } - # - # pub fn cpointer_null_return_test() [*c]TestStruct { - # return null; - # } - # """ - # - # describe "when returning a cpointer" do - # # we can guess what the correct cpointer should be based on the - # # type, in some cases. - # - # test "a u8 will be marshalled from a null terminated binary" do - # assert "abc" == cpointer_u8_return_test() - # end - # - # test "a u8 can be marshalled into a charlist instead" do - # assert ~C'abc' == cpointer_u8_list_return_test() - # end - # - # test "a struct can be marshalled into a struct" do - # assert [%{value: 47}] == cpointer_struct_list_return_test() - # end - # - # test "a struct pointer list can be null terminated" do - # assert %{value: 47} == cpointer_struct_return_test() - # end - # - # test "null can be returned" do - # assert is_nil(cpointer_null_return_test()) - # end - # end + use Zig, + otp_app: :zigler, + leak_check: false, + nifs: [ + {:cpointer_u8_list_return_test, return: :list}, + ... + ] + + ## C pointers as single pointers + ## C pointers can be a single pointer to a struct, which makes it a "mutable" struct. + ~Z""" + pub const TestStruct = extern struct { value: i32 }; + + pub fn cpointer_test(passed: [*c]TestStruct) ?i32 { + if (passed) |unwrapped| { + return unwrapped.*.value; + } else { + return null; + } + } + """ + + describe "for a struct cpointer" do + test "you can pass a map" do + assert 47 = cpointer_test(%{value: 47}) + end + + test "you can pass null" do + assert is_nil(cpointer_test(nil)) + end + + test "you can't pass a keyword list" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | list(map | keyword) (for `[*c]TestStruct`)\n got: `[value: 47]`\n at index 0:\n | expected: map | keyword (for `TestStruct`)\n | got: `{:value, 47}`\n", + fn -> + cpointer_test(value: 47) + end + end + + test "you can't pass some other term" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | list(map | keyword) (for `[*c]TestStruct`)\n got: `:foo`\n note: [*c]TestStruct can take the atom `nil` but no other atom\n", + fn -> + cpointer_test(:foo) + end + end + + test "you can't pass a bad value term" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | list(map | keyword) (for `[*c]TestStruct`)\n got: `%{value: :foo}`\n in field `:value`:\n | expected: integer (for `i32`)\n | got: `:foo`\n", + fn -> + cpointer_test(%{value: :foo}) + end + end + end + + ~Z""" + pub fn cpointer_list_test(list: [*c]u8) ?u32 { + var sum: u32 = 0; + if (list) |_| { + for (list[0..3]) |item| {sum += item;} + return sum; + } else return null; + } + + pub fn cpointer_struct_list_test(list: [*c]TestStruct) ?i32 { + var sum: i32 = 0; + for (list[0..3]) |item| {sum += item.value;} + return sum; + } + """ + + describe "for a list cpointer" do + test "you can pass a list" do + assert 6 == cpointer_list_test([1, 2, 3]) + end + + test "you can pass null" do + assert is_nil(cpointer_list_test(nil)) + end + + test "you can pass a string for u8" do + assert Enum.sum(~C"abc") == cpointer_list_test("abc") + end + + test "you can pass a list of structs" do + assert 6 = cpointer_struct_list_test([%{value: 1}, %{value: 2}, %{value: 3}]) + end + + test "you can't pass a non-list term" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: binary | list(integer) (for `[*c]u8`)\n got: `:foo`\n note: [*c]u8 can take the atom `nil` but no other atom\n", + fn -> + cpointer_list_test(:foo) + end + end + + test "list item should be correctly typed" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: binary | list(integer) (for `[*c]u8`)\n got: `[:foo]`\n at index 0:\n | expected: integer (for `u8`)\n | got: `:foo`\n", + fn -> + cpointer_list_test([:foo]) + end + end + end + + ~Z""" + var u8_array_list = [_]u8{'a', 'b', 'c', 0}; + pub fn cpointer_u8_return_test() [*c]u8 { + return &u8_array_list; + } + + pub fn cpointer_u8_list_return_test() [*c]u8 { + return &u8_array_list; + } + + var result_struct = TestStruct{.value = 47}; + + pub fn cpointer_struct_return_test() [*c]TestStruct { + return &result_struct; + } + + var struct_list: [2][*c]TestStruct = .{&result_struct, null}; + + pub fn cpointer_struct_list_return_test() [*c][*c]TestStruct { + return &struct_list; + } + + pub fn cpointer_null_return_test() [*c]TestStruct { + return null; + } + """ + + describe "when returning a cpointer" do + # we can guess what the correct cpointer should be based on the + # type, in some cases. + + test "a u8 will be marshalled from a null terminated binary" do + assert "abc" == cpointer_u8_return_test() + end + + test "a u8 can be marshalled into a charlist instead" do + assert ~C'abc' == cpointer_u8_list_return_test() + end + + test "a struct can be marshalled into a struct" do + assert [%{value: 47}] == cpointer_struct_list_return_test() + end + + test "a struct pointer list can be null terminated" do + assert %{value: 47} == cpointer_struct_return_test() + end + + test "null can be returned" do + assert is_nil(cpointer_null_return_test()) + end + end end diff --git a/test/integration/types/enum_test.exs b/test/integration/types/enum_test.exs index 3af78768..05922b38 100644 --- a/test/integration/types/enum_test.exs +++ b/test/integration/types/enum_test.exs @@ -1,47 +1,44 @@ defmodule ZiglerTest.Types.EnumTest do use ZiglerTest.IntegrationCase, async: true - @moduletag :skip - test "restore this test" - - # use Zig, - # otp_app: :zigler - # - # ~Z""" - # pub const EnumType = enum{ foo, bar }; - # - # pub fn untagged_swap(value: EnumType) EnumType { - # return if (value == .foo) .bar else .foo; - # } - # """ - # - # describe "given an enum" do - # test "you can pass in atoms to get the value out" do - # assert :foo = untagged_swap(:bar) - # assert :bar = untagged_swap(:foo) - # end - # - # test "you can pass in integers and they'll be coerced" do - # assert :foo = untagged_swap(1) - # assert :bar = untagged_swap(0) - # end - # - # test "if you try to use something that isn't an atom or integer" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `\"foo\"`\n", - # fn -> untagged_swap("foo") end - # end - # - # test "if you try to use an invalid atom" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n note: not an atom value for EnumType (should be one of `[:foo, :bar]`)\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `:zag`\n", - # fn -> untagged_swap(:zag) end - # end - # - # test "if you try to use an invalid number" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n note: not an integer value for EnumType (should be one of `[0, 1]`)\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `42`\n", - # fn -> untagged_swap(42) end - # end - # end + use Zig, + otp_app: :zigler, dump: true + + ~Z""" + pub const EnumType = enum{ foo, bar }; + + pub fn untagged_swap(value: EnumType) EnumType { + return if (value == .foo) .bar else .foo; + } + """ + + describe "given an enum" do + test "you can pass in atoms to get the value out" do + assert :foo = untagged_swap(:bar) + assert :bar = untagged_swap(:foo) + end + + test "you can pass in integers and they'll be coerced" do + assert :foo = untagged_swap(1) + assert :bar = untagged_swap(0) + end + + test "if you try to use something that isn't an atom or integer" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `\"foo\"`\n", + fn -> untagged_swap("foo") end + end + + test "if you try to use an invalid atom" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n note: not an atom value for EnumType (should be one of `[:foo, :bar]`)\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `:zag`\n", + fn -> untagged_swap(:zag) end + end + + test "if you try to use an invalid number" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n note: not an integer value for EnumType (should be one of `[0, 1]`)\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `42`\n", + fn -> untagged_swap(42) end + end + end end diff --git a/test/integration/types/struct_test.exs b/test/integration/types/struct_test.exs index 91bdbd14..9f273e71 100644 --- a/test/integration/types/struct_test.exs +++ b/test/integration/types/struct_test.exs @@ -1,123 +1,120 @@ defmodule ZiglerTest.Types.StructTest do use ZiglerTest.IntegrationCase, async: true - @moduletag :skip - test "restore this!" - - # use Zig, - # otp_app: :zigler - # - # ~Z""" - # pub const TestStruct = struct { - # value: u64 - # }; - # - # pub fn struct_test(s: TestStruct) TestStruct { - # return .{.value = s.value + 1}; - # } - # """ - # - # describe "for a basic struct" do - # test "can be called as a map" do - # assert %{value: 48} == struct_test(%{value: 47}) - # end - # - # test "can be called as a keyword list" do - # assert %{value: 48} == struct_test(value: 47) - # end - # - # test "extraneous values in a map are ignored and tolerated" do - # assert %{value: 48} == struct_test(%{value: 47, foo: "bar"}) - # end - # - # @tag :skip - # test "extraneous values in a keyword list are ignored and tolerated" do - # assert %{value: 48} == struct_test(%{value: 47, foo: "bar"}) - # end - # - # test "missing required values in a map are not tolerated" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `%{}`\n note: TestStruct requires the field `:value`, which is missing.)\n", - # fn -> struct_test(%{}) end - # end - # - # test "missing required values in a keyword list are not tolerated" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `[]`\n note: TestStruct requires the field `:value`, which is missing.)\n", - # fn -> struct_test([]) end - # end - # - # test "incorrect value types in a map are not tolerated" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `%{value: \"foo\"}`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", - # fn -> struct_test(%{value: "foo"}) end - # end - # - # test "incorrect value types in a keyword list are not tolerated" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `[value: \"foo\"]`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", - # fn -> struct_test(value: "foo") end - # end - # end - # - # ~Z""" - # pub const default_struct = struct { - # value: u64 = 47 - # }; - # - # pub fn default_struct_test(s: default_struct) default_struct { - # return .{.value = s.value + 1}; - # } - # """ - # - # describe "for a struct with a default" do - # test "can be called as a map" do - # assert %{value: 48} == default_struct_test(%{value: 47}) - # end - # - # test "can be called as a keyword list" do - # assert %{value: 48} == default_struct_test(value: 47) - # end - # - # test "extraneous values in a map are ignored and tolerated" do - # assert %{value: 48} == default_struct_test(%{value: 47, foo: "bar"}) - # end - # - # test "extraneous values in a keyword list are ignored and tolerated" do - # assert %{value: 48} == default_struct_test(%{value: 47, foo: "bar"}) - # end - # - # test "missing default values in a map are tolerated" do - # assert %{value: 48} == default_struct_test(%{}) - # end - # - # test "missing default values in a keyword list are tolerated" do - # assert %{value: 48} == default_struct_test([]) - # end - # - # test "incorrect value types in a map are not tolerated" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `default_struct`)\n got: `%{value: \"foo\"}`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", - # fn -> default_struct_test(%{value: "foo"}) end - # end - # - # test "incorrect value types in a keyword list are not tolerated" do - # assert_raise ArgumentError, - # "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `default_struct`)\n got: `[value: \"foo\"]`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", - # fn -> default_struct_test(value: "foo") end - # end - # end - # - # ~Z""" - # pub fn mutable_struct_test(s: *TestStruct) *TestStruct { - # s.value += 1; - # return s; - # } - # """ - # - # describe "structs can be pseudo-mutable" do - # test "called as a map" do - # assert %{value: 48} == mutable_struct_test(%{value: 47}) - # end - # end + use Zig, + otp_app: :zigler + + ~Z""" + pub const TestStruct = struct { + value: u64 + }; + + pub fn struct_test(s: TestStruct) TestStruct { + return .{.value = s.value + 1}; + } + """ + + describe "for a basic struct" do + test "can be called as a map" do + assert %{value: 48} == struct_test(%{value: 47}) + end + + test "can be called as a keyword list" do + assert %{value: 48} == struct_test(value: 47) + end + + test "extraneous values in a map are ignored and tolerated" do + assert %{value: 48} == struct_test(%{value: 47, foo: "bar"}) + end + + @tag :skip + test "extraneous values in a keyword list are ignored and tolerated" do + assert %{value: 48} == struct_test(%{value: 47, foo: "bar"}) + end + + test "missing required values in a map are not tolerated" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `%{}`\n note: TestStruct requires the field `:value`, which is missing.)\n", + fn -> struct_test(%{}) end + end + + test "missing required values in a keyword list are not tolerated" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `[]`\n note: TestStruct requires the field `:value`, which is missing.)\n", + fn -> struct_test([]) end + end + + test "incorrect value types in a map are not tolerated" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `%{value: \"foo\"}`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", + fn -> struct_test(%{value: "foo"}) end + end + + test "incorrect value types in a keyword list are not tolerated" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `TestStruct`)\n got: `[value: \"foo\"]`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", + fn -> struct_test(value: "foo") end + end + end + + ~Z""" + pub const default_struct = struct { + value: u64 = 47 + }; + + pub fn default_struct_test(s: default_struct) default_struct { + return .{.value = s.value + 1}; + } + """ + + describe "for a struct with a default" do + test "can be called as a map" do + assert %{value: 48} == default_struct_test(%{value: 47}) + end + + test "can be called as a keyword list" do + assert %{value: 48} == default_struct_test(value: 47) + end + + test "extraneous values in a map are ignored and tolerated" do + assert %{value: 48} == default_struct_test(%{value: 47, foo: "bar"}) + end + + test "extraneous values in a keyword list are ignored and tolerated" do + assert %{value: 48} == default_struct_test(%{value: 47, foo: "bar"}) + end + + test "missing default values in a map are tolerated" do + assert %{value: 48} == default_struct_test(%{}) + end + + test "missing default values in a keyword list are tolerated" do + assert %{value: 48} == default_struct_test([]) + end + + test "incorrect value types in a map are not tolerated" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `default_struct`)\n got: `%{value: \"foo\"}`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", + fn -> default_struct_test(%{value: "foo"}) end + end + + test "incorrect value types in a keyword list are not tolerated" do + assert_raise ArgumentError, + "errors were found at the given arguments:\n\n * 1st argument: \n\n expected: map | keyword (for `default_struct`)\n got: `[value: \"foo\"]`\n in field `:value`:\n | expected: integer (for `u64`)\n | got: `\"foo\"`\n", + fn -> default_struct_test(value: "foo") end + end + end + + ~Z""" + pub fn mutable_struct_test(s: *TestStruct) *TestStruct { + s.value += 1; + return s; + } + """ + + describe "structs can be pseudo-mutable" do + test "called as a map" do + assert %{value: 48} == mutable_struct_test(%{value: 47}) + end + end end diff --git a/test/integration/types/void_test.exs b/test/integration/types/void_test.exs index 2391df4e..57dc3493 100644 --- a/test/integration/types/void_test.exs +++ b/test/integration/types/void_test.exs @@ -1,19 +1,16 @@ defmodule ZiglerTest.Types.VoidTest do use ZiglerTest.IntegrationCase, async: true - @moduletag :skip - test "restore this!" + use Zig, + otp_app: :zigler - # use Zig, - # otp_app: :zigler - # - # ~Z""" - # pub fn void_test() void {} - # """ - # - # describe "for a basic void" do - # test "it returns `:ok`" do - # assert :ok == void_test() - # end - # end + ~Z""" + pub fn void_test() void {} + """ + + describe "for a basic void" do + test "it returns `:ok`" do + assert :ok == void_test() + end + end end