Skip to content

Commit

Permalink
completes sensible errros for missing on_load callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
ityonemo committed May 19, 2024
1 parent 7c94902 commit 57e09df
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 27 deletions.
15 changes: 4 additions & 11 deletions lib/zig/_module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Zig.Module do
@impl true
defdelegate fetch(function, key), to: Map

@enforce_keys [:otp_app, :module, :file]
@enforce_keys [:otp_app, :module, :file, :line]

defstruct @enforce_keys ++
[
Expand Down Expand Up @@ -49,6 +49,7 @@ defmodule Zig.Module do
otp_app: atom(),
module: atom(),
file: Path.t(),
line: non_neg_integer(),
on_load: atom(),
upgrade: atom(),
easy_c: nil | Path.t(),
Expand Down Expand Up @@ -107,7 +108,8 @@ defmodule Zig.Module do
|> Keyword.merge(
default_nif_opts: Keyword.take(opts, @defaultable_nif_opts),
module: caller.module,
file: caller.file
file: caller.file,
line: caller.line
)
|> normalize_options()
|> then(&struct!(__MODULE__, &1))
Expand All @@ -117,15 +119,6 @@ defmodule Zig.Module do
opts
|> obtain_version
|> Keyword.update(:c, nil, &C.new/1)

# |> normalize_include_dirs
# |> normalize_src
# |> normalize_nifs
# |> normalize_libs
# |> normalize_build_opts
# |> normalize_include_dirs
# |> normalize_src
# |> EasyC.normalize_aliasing()
end

defp obtain_version(opts) do
Expand Down
11 changes: 7 additions & 4 deletions lib/zig/manifest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ defmodule Zig.Manifest do
|> Enum.with_index(1)
|> Enum.flat_map(fn
{"// ref " <> rest, anchor} ->
[file, line] = String.split(rest, ":")

[{anchor, {Path.absname(file), String.to_integer(line)}}]
case String.split(rest, ":") do
["nofile", ""] ->
[{anchor, {"nofile", 0}}]
[file, line] ->
[{anchor, {Path.absname(file), String.to_integer(line)}}]
end

_ ->
[]
Expand All @@ -66,7 +69,7 @@ defmodule Zig.Manifest do
quote do
defmodule unquote(manifest_module) do
require Zig.Manifest
Zig.Manifest.resolver(unquote(manifest), unquote(module.module_code_path), :def)
Zig.Manifest.resolver(unquote(manifest), unquote(module.zig_code_path), :def)
end
end
)
Expand Down
42 changes: 30 additions & 12 deletions lib/zig/sema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,21 @@ defmodule Zig.Sema do
defp assign_callbacks(sema, module) do
sema
|> Map.put("callbacks", [])
|> then(fn sema -> Enum.reduce(module.callbacks, sema, &move_callback/2) end)
|> then(fn sema -> Enum.reduce(module.callbacks, sema, &move_callback(&1, &2, module)) end)
end

defp move_callback({type, name}, %{"functions" => functions, "callbacks" => callbacks} = sema) do
{new_functions, new_callback} = find_remove(functions, [], "#{name}", type)
defp move_callback({type, name}, %{"functions" => functions, "callbacks" => callbacks} = sema, module) do
{new_functions, new_callback} = find_remove(functions, [], "#{name}", type, module)
%{sema | "functions" => new_functions, "callbacks" => [{type, new_callback} | callbacks]}
end

defp find_remove([%{"name" => name} = callback | rest], so_far, name, _type),
defp find_remove([%{"name" => name} = callback | rest], so_far, name, _type, _module),
do: {Enum.reverse(so_far, rest), callback}

defp find_remove([other | rest], so_far, name, type),
do: find_remove(rest, [other | so_far], name, type)
defp find_remove([other | rest], so_far, name, type, module),
do: find_remove(rest, [other | so_far], name, type, module)

defp find_remove([], _, name, type) do
raise CompileError, description: "#{type} callback #{name} not found"
end
defp find_remove([], so_far, _name, _type, _module), do: {so_far, nil}

# removes "ignored" and "callback" functions from the semantic analysis.
defp reject_ignored(json, module) do
Expand All @@ -86,7 +84,7 @@ defmodule Zig.Sema do
types: Enum.map(types, &type_from_json(&1, module.module)),
decls: Enum.map(decls, &const_from_json/1),
callbacks: Enum.map(callbacks, fn
{type, json} -> {type, Function.from_json(json, module.module)}
{type, json} -> {type, json && Function.from_json(json, module.module)}
end)
}
end
Expand All @@ -111,8 +109,6 @@ defmodule Zig.Sema do
# information. Also strips "auto" from the nif information to provide a finalized
# keyword list of functions with their options.
def analyze_file!(%{sema: %{functions: functions, types: _types}} = module) do
# Enum.each(functions, &validate_usable!(&1, types, module))

# `nifs` option could either be {:auto, keyword} which means that the full
# list of functions should be derived from the semantic analysis, determining
# which functions have `pub` declaration, with certain functions optionally
Expand All @@ -121,6 +117,9 @@ defmodule Zig.Sema do
# it could also be just a list of functions with their specifications, in
# which case those are the *only* functions that will be included.

# check for invalid callbacks
check_invalid_callbacks!(module)

nifs =
case module.nifs do
{:auto, specified_fns} ->
Expand Down Expand Up @@ -240,4 +239,23 @@ defmodule Zig.Sema do
line: nif.line
end
end

defp check_invalid_callbacks!(module) do
for {type, nil} <- module.sema.callbacks do
name = module.callbacks[type]

# search to see if the name exists in the parsed code.
for %{name: ^name, pub: false, location: {line, _col}} <- module.parsed.code do

{file, line} = module.manifest_module.__resolve(%{file_name: module.zig_code_path, line: line})

raise CompileError,
description: "#{type} callback #{name} must be declared `pub`",
file: file,
line: line
end

raise CompileError, description: "#{type} callback #{name} not found", file: module.file, line: module.line
end
end
end
33 changes: 33 additions & 0 deletions test/integration/callbacks/missing_on_load_callback_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule ZiglerTest.Callbacks.MissingOnLoadCallbackTest do
# this is a test of the "automatic" on_load function.

use ZiglerTest.IntegrationCase, async: true

test "compiler error when on_load function is missing" do
assert_raise CompileError, "nofile: on_load callback foo not found", fn ->
Code.compile_quoted(quote do
defmodule ZiglerTest.MissingOnloadCallback do
use Zig, otp_app: :zigler, callbacks: [on_load: :foo]
~Z"""
pub fn bar() u8 { return 47; }
"""
end
end)
end
end

test "compiler error when on_load function is not pub" do
assert_raise CompileError, "nofile:2: on_load callback foo must be declared `pub`", fn ->
Code.compile_quoted(quote do
defmodule ZiglerTest.NotPubOnloadCallback do
use Zig, otp_app: :zigler, callbacks: [on_load: :foo]
~Z"""
const beam = @import("beam");
fn foo(_: [*c]?*anyopaque, _: beam.term) c_int { return 0; }
pub fn bar() u8 { return 47; }
"""
end
end)
end
end
end

0 comments on commit 57e09df

Please sign in to comment.