Current section
Files
Jump to
Current section
Files
lib/firebird/typed_call.ex
defmodule Firebird.TypedCall do
@moduledoc """
Type-checked WASM function calls.
Validates argument count and types before calling WASM functions,
providing clear error messages instead of cryptic runtime failures.
## Examples
{:ok, instance} = Firebird.load("math.wasm")
# Validates arguments match the function signature
{:ok, [8]} = Firebird.TypedCall.call(instance, :add, [5, 3])
# Returns clear error for wrong arity
{:error, {:arity_mismatch, expected: 2, got: 1}} =
Firebird.TypedCall.call(instance, :add, [5])
# Returns clear error for wrong types
{:error, {:type_mismatch, 0, expected: :i32, got: "hello"}} =
Firebird.TypedCall.call(instance, :add, ["hello", 3])
"""
@doc """
Call a WASM function with type validation.
Checks argument count and basic type compatibility before making the call.
"""
@spec call(pid(), atom() | String.t(), list()) :: {:ok, list()} | {:error, term()}
def call(instance, function, args) do
with :ok <- validate_args(instance, function, args) do
Firebird.call(instance, function, args)
end
end
@doc """
Call a WASM function with type validation, raising on error.
"""
@spec call!(pid(), atom() | String.t(), list()) :: list()
def call!(instance, function, args) do
case call(instance, function, args) do
{:ok, result} -> result
{:error, reason} -> raise build_error(function, reason)
end
end
@doc """
Validate arguments for a WASM function without calling it.
Returns `:ok` if valid, or `{:error, reason}` with details.
"""
@spec validate_args(pid(), atom() | String.t(), list()) :: :ok | {:error, term()}
def validate_args(instance, function, args) do
func_str = to_string(function)
unless Firebird.function_exists?(instance, func_str) do
{:error, {:function_not_found, func_str}}
else
case Firebird.function_type(instance, func_str) do
{:ok, {params, _results}} ->
cond do
length(args) != length(params) ->
{:error, {:arity_mismatch, expected: length(params), got: length(args)}}
true ->
validate_types(args, params)
end
{:error, _} ->
# Can't get type info, skip validation
:ok
end
end
end
# Private helpers
defp validate_types(args, params) do
args
|> Enum.zip(params)
|> Enum.with_index()
|> Enum.reduce_while(:ok, fn {{arg, expected_type}, index}, :ok ->
if type_compatible?(arg, expected_type) do
{:cont, :ok}
else
{:halt, {:error, {:type_mismatch, index, expected: expected_type, got: type_of(arg)}}}
end
end)
end
defp type_compatible?(arg, :i32) when is_integer(arg), do: true
defp type_compatible?(arg, :i64) when is_integer(arg), do: true
defp type_compatible?(arg, :f32) when is_number(arg), do: true
defp type_compatible?(arg, :f64) when is_number(arg), do: true
defp type_compatible?(_, _), do: false
defp type_of(arg) when is_integer(arg), do: :integer
defp type_of(arg) when is_float(arg), do: :float
defp type_of(arg) when is_binary(arg), do: :string
defp type_of(arg) when is_atom(arg), do: :atom
defp type_of(arg) when is_list(arg), do: :list
defp type_of(_), do: :unknown
defp build_error(function, {:arity_mismatch, opts}) do
Firebird.WasmError.arity_mismatch(function, opts[:expected], opts[:got])
end
defp build_error(_function, {:type_mismatch, idx, opts}) do
Firebird.WasmError.type_mismatch(
opts[:function] || "unknown",
idx,
opts[:expected],
opts[:got]
)
end
defp build_error(_function, {:function_not_found, name}) do
Firebird.WasmError.function_not_found(name, [])
end
defp build_error(function, reason) do
%Firebird.WasmError{
message: "TypedCall error for '#{function}': #{inspect(reason)}",
reason: reason,
function: function
}
end
end