Skip to content

Commit

Permalink
type_spec: return tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ityonemo committed May 5, 2024
1 parent 1c99662 commit 52a437f
Show file tree
Hide file tree
Showing 18 changed files with 409 additions and 601 deletions.
2 changes: 1 addition & 1 deletion guides/2-collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ end
```

Conversely, for u8 array-like datatypes, selecting `.output = .list` will
result in outputting a charlist.
result in outputting a list.

You can also automatically marshal as binary by using
[nif options](4-nif_options.html#binary-output)
Expand Down
8 changes: 4 additions & 4 deletions guides/4-nif_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ defmodule ReturnTypeTest do
otp_app: :zigler,
nifs: [
returns_binary: [return: :binary],
returns_charlist: [return: :list]
returns_list: [return: :list]
]
~Z"""
pub fn returns_binary() [3]u16 {
return [3]u16{47, 48, 49};
}

pub fn returns_charlist() []const u8 {
pub fn returns_list() []const u8 {
return "Hello world!";
}
"""
Expand All @@ -132,8 +132,8 @@ defmodule ReturnTypeTest do
assert <<47, 0, 48, 0, 49, 0>> = returns_binary()
end
test "returns charlist" do
assert ~C'Hello world!' = returns_charlist()
test "returns list" do
assert ~C'Hello world!' = returns_list()
end
end
```
Expand Down
4 changes: 2 additions & 2 deletions lib/zig/type/array.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule Zig.Type.Array do

def render_elixir_spec(%{child: ~t(u8)} = type, :return, opts) do
# u8 defaults to binary
case Keyword.fetch!(opts, :type) do
case opts.as do
:list ->
[Type.render_elixir_spec(~t(u8), :return, opts)]

Expand All @@ -50,7 +50,7 @@ defmodule Zig.Type.Array do
# other types defaults to binary
binary_form = binary_form(child, known_length(type))

case Keyword.fetch!(opts, :type) do
case opts.as do
:binary when not is_nil(binary_form) ->
binary_form

Expand Down
26 changes: 13 additions & 13 deletions lib/zig/type/cpointer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,53 +53,53 @@ defmodule Zig.Type.Cpointer do

def render_elixir_spec(%{child: ~t(u8)}, :return, opts) do
# assumed to be a null-terminated string
case {opts[:length], Keyword.fetch!(opts, :type)} do
{_, :list} ->
case opts do
%{as: :list} ->
quote context: Elixir do
[0..255]
end

{length, type} when not is_nil(length) and type in ~w(default binary)a ->
%{length: length, as: type} when not is_integer(length) and type in ~w(default binary)a ->
quote context: Elixir do
<<_::unquote(length * 8)>>
end

{_, type} when type in ~w(default binary)a ->
%{as: type} when type in ~w(default binary)a ->
quote do
binary()
end
end
end

def render_elixir_spec(%{child: child}, :return, opts) do
case {Keyword.fetch(opts, :length), Keyword.fetch!(opts, :type)} do
{{:ok, _}, :default} ->
case opts do
%{as: :default} ->
[Type.render_elixir_spec(child, :return, opts)]

{{:ok, {:arg, _}}, :binary} ->
%{as: :binary, length: {:arg, _}} ->
# this is the case where the length is drawn from one of the arguments
# to the function.
quote do
<<_::_*unquote(chunk_size(child))>>
end

{{:ok, number}, :binary} ->
%{as: :binary, length: length} when is_integer(length) ->
quote do
<<_::unquote(number * chunk_size(child))>>
<<_::unquote(length * chunk_size(child))>>
end

{:error, _} when child.__struct__ == Type.Struct ->
_ when child.__struct__ == Type.Struct ->
Type.render_elixir_spec(child, :return, opts)

{:error, _} when child == %__MODULE__{child: ~t(u8)} ->
_ when child == %__MODULE__{child: ~t(u8)} ->
quote do
[binary()]
end

{:error, _} when child.__struct__ == __MODULE__ ->
_ when child.__struct__ == __MODULE__ ->
[Type.render_elixir_spec(child.child, :return, opts)]

{:error, _} ->
_ ->
raise "missing length not allowed"
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/zig/type/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ defmodule Zig.Type.Error do
Type.spec(child, context, opts)
end

def render_return(_, _), do: Type._default_return()

def of(child), do: %__MODULE__{child: child}
end
2 changes: 1 addition & 1 deletion lib/zig/type/manypointer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule Zig.Type.Manypointer do

# only manypointers of [*:0]u8 are allowed to be returned.
def render_elixir_spec(%{child: ~t(u8), has_sentinel?: true}, :return, opts) do
case Keyword.fetch!(opts, :type) do
case opts.as do
:list ->
[Type.render_elixir_spec(~t(u8), :return, opts)]

Expand Down
1 change: 1 addition & 0 deletions lib/zig/type/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ defmodule Zig.Type.Resource do
def can_cleanup?(_), do: true

def return_allowed?(_resource), do: true
def param_allowed?(_), do: true
end
2 changes: 1 addition & 1 deletion lib/zig/type/slice.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ defmodule Zig.Type.Slice do
{:return, %{as: :binary}} ->
binary_form(child) || raise "unreachable"

{:return, type} when type in ~w(default charlist)a ->
{:return, %{as: type}} when type in ~w(default list)a ->
[Type.render_elixir_spec(child, :return, [])]

{:param, _} ->
Expand Down
4 changes: 2 additions & 2 deletions lib/zig/type/struct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ defmodule Zig.Type.Struct do
def render_elixir_spec(struct, :return, opts) do
binary_form = binary_form(struct)

case Keyword.fetch!(opts, :type) do
case opts.as do
:binary when not is_nil(binary_form) ->
binary_form

t when t in ~w(charlist binary default)a ->
t when t in ~w(list binary default)a ->
all_fields =
struct.optional
|> Map.merge(struct.required)
Expand Down
55 changes: 29 additions & 26 deletions test/integration/alias_test.exs
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
defmodule ZiglerTest.AliasTest do
use ZiglerTest.IntegrationCase, async: true

use Zig,
otp_app: :zigler,
nifs: [
...,
renamed: [alias: :ok]
]
@moduletag :skip
test "restore"

~Z"""
const beam = @import("beam");
pub fn ok() void { }
"""

test "intermediate content actually has the aliased form" do
assert __DIR__
|> Path.join(".#{__MODULE__}.zig")
|> File.read!()
|> Kernel.=~("pub const renamed = ok;")
end

test "aliased call" do
assert :ok = ok()
end

test "renamed call" do
assert :ok = renamed()
end
# use Zig,
# otp_app: :zigler,
# nifs: [
# ...,
# renamed: [alias: :ok]
# ]
#
# ~Z"""
# const beam = @import("beam");
#
# pub fn ok() void { }
# """
#
# test "intermediate content actually has the aliased form" do
# assert __DIR__
# |> Path.join(".#{__MODULE__}.zig")
# |> File.read!()
# |> Kernel.=~("pub const renamed = ok;")
# end
#
# test "aliased call" do
# assert :ok = ok()
# end
#
# test "renamed call" do
# assert :ok = renamed()
# end
end
71 changes: 36 additions & 35 deletions test/integration/concurrency/threaded_automatic_erroring_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,40 @@ defmodule ZiglerTest.Concurrency.ThreadedAutomaticErroringTest do
use ZiglerTest.IntegrationCase, async: true

@moduletag [threaded: true, erroring: true]

use Zig, otp_app: :zigler, nifs: [threaded: [:threaded]]

~Z"""
const beam = @import("beam");
const ThreadError = error{BadNumber};
pub fn threaded(number: i32) !i32 {
if (number == 42) { return error.BadNumber; }
return number + 1;
}
"""

test "threaded function can succeed" do
assert 48 = threaded(47)
end

# note the line numbers might not be correct, hence the skip.
@tag :skip
test "threaded function can error" do
error =
try do
threaded(42)
rescue
e in ErlangError ->
%{payload: e.original, stacktrace: __STACKTRACE__}
end

assert %{payload: :BadNumber, stacktrace: [head | _]} = error

expected_file = Path.absname(__ENV__.file)

assert {__MODULE__, :threaded, [:...], [file: ^expected_file, line: 13]} = head
end
test "restore"

# use Zig, otp_app: :zigler, nifs: [threaded: [:threaded]]
#
# ~Z"""
# const beam = @import("beam");
#
# const ThreadError = error{BadNumber};
#
# pub fn threaded(number: i32) !i32 {
# if (number == 42) { return error.BadNumber; }
# return number + 1;
# }
# """
#
# test "threaded function can succeed" do
# assert 48 = threaded(47)
# end
#
# # note the line numbers might not be correct, hence the skip.
# @tag :skip
# test "threaded function can error" do
# error =
# try do
# threaded(42)
# rescue
# e in ErlangError ->
# %{payload: e.original, stacktrace: __STACKTRACE__}
# end
#
# assert %{payload: :BadNumber, stacktrace: [head | _]} = error
#
# expected_file = Path.absname(__ENV__.file)
#
# assert {__MODULE__, :threaded, [:...], [file: ^expected_file, line: 13]} = head
# end
end
67 changes: 34 additions & 33 deletions test/integration/concurrency/threaded_automatic_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,39 @@ defmodule ZiglerTest.Concurrency.ThreadedAutomaticTest do
use ZiglerTest.IntegrationCase, async: true

@moduletag :threaded
test "restore"

use Zig, otp_app: :zigler, nifs: [threaded: [:threaded]]

~Z"""
const std = @import("std");
const beam = @import("beam");
const e = @import("erl_nif");
pub fn threaded(should_quit: bool, resp: beam.pid) void {
var i: u32 = 0;
while (true) : (i += 1) {
if (i > 5000 and should_quit) {
break;
}
_ = beam.yield() catch {
_ = beam.send(resp, .killed, .{}) catch unreachable;
break;
};
}
}
"""

test "threaded function" do
assert :ok = threaded(true, self())
refute_receive :killed
end

test "threaded function gc's" do
this = self()
pid = spawn(fn -> threaded(false, this) end)
:erlang.garbage_collect(pid)
Process.exit(pid, :kill)
assert_receive :killed, 500
end
# use Zig, otp_app: :zigler, nifs: [threaded: [:threaded]]
#
# ~Z"""
# const std = @import("std");
# const beam = @import("beam");
# const e = @import("erl_nif");
# pub fn threaded(should_quit: bool, resp: beam.pid) void {
# var i: u32 = 0;
# while (true) : (i += 1) {
# if (i > 5000 and should_quit) {
# break;
# }
#
# _ = beam.yield() catch {
# _ = beam.send(resp, .killed, .{}) catch unreachable;
# break;
# };
# }
# }
# """
#
# test "threaded function" do
# assert :ok = threaded(true, self())
# refute_receive :killed
# end
#
# test "threaded function gc's" do
# this = self()
# pid = spawn(fn -> threaded(false, this) end)
# :erlang.garbage_collect(pid)
# Process.exit(pid, :kill)
# assert_receive :killed, 500
# end
end
2 changes: 1 addition & 1 deletion test/integration/concurrency/threaded_manual_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule ZiglerTest.Concurrency.ThreadedManualTest do
use ZiglerTest.IntegrationCase, async: true

@moduletag :threaded
@moduletag :skip
test "restore"

# use Zig, otp_app: :zigler, cleanup: false, resources: [:ThreadResource]
#
Expand Down
Loading

0 comments on commit 52a437f

Please sign in to comment.