From d89c5425445ee974a2660ed3be5c32d68dae5c07 Mon Sep 17 00:00:00 2001 From: Benjamin Danklin <34633373+bdanklin@users.noreply.github.com> Date: Mon, 19 Jun 2023 10:29:44 +1000 Subject: [PATCH 1/3] A proof of concept for auto generated bangs. This can also be applied to async functions. eg take the function edit_message. adding @unsafe and @async attributes to the function can handle generating edit_message! and edit_message_async respectively. This only contains the code for the banged versions however should the implementaion be approved the async code is trivial. This also allows all the manual bang functions to be remove from the api module. (Approx 70) --- lib/nostrum/api.ex | 4 ++ lib/nostrum/helpers/unsafe_helpers.ex | 96 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 lib/nostrum/helpers/unsafe_helpers.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 1bda61c04..3ac41a299 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -65,6 +65,9 @@ defmodule Nostrum.Api do alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role, ScheduledEvent} alias Nostrum.Shard.{Session, Supervisor} + use Nostrum.UnsafeHelpers, + handler: :bangify + @typedoc """ Represents a failed response from the API. @@ -162,6 +165,7 @@ defmodule Nostrum.Api do - `type` - The type of status to show. 0 (Playing) | 1 (Streaming) | 2 (Listening) | 3 (Watching) - `stream` - URL of twitch.tv stream """ + @unsafe {:update_status, [:pid, :status, :game, :type, :stream]} @spec update_shard_status(pid, status, String.t(), integer, String.t() | nil) :: :ok def update_shard_status(pid, status, game, type \\ 0, stream \\ nil) do Session.update_status(pid, to_string(status), game, stream, type) diff --git a/lib/nostrum/helpers/unsafe_helpers.ex b/lib/nostrum/helpers/unsafe_helpers.ex new file mode 100644 index 000000000..4201c617c --- /dev/null +++ b/lib/nostrum/helpers/unsafe_helpers.ex @@ -0,0 +1,96 @@ +defmodule Nostrum.UnsafeHelpers do + @moduledoc false + ############################################################################# + ## Taken from [`Unsafe`](https://hex.pm/packages/unsafe) + ## Copyright (c) 2017 Isaac Whitfield + ## + + defmacro __using__(options) do + quote location: :keep do + @before_compile unquote(__MODULE__) + @unsafe_options unquote(options) + + Module.register_attribute(__MODULE__, :unsafe, accumulate: true) + end + end + + defmacro __before_compile__(%{module: module} = env) do + binding = Module.get_attribute(module, :unsafe) + + options = + module + |> Module.get_attribute(:unsafe_options) + |> Kernel.||([]) + + compile!(env, binding, options) + end + + @type arities :: arity | [arity] + @type binding :: {atom, arities} | {atom, arities, handler} + @type handler :: atom | {atom, atom} + + @spec compile!(Macro.Env.t(), binding | [binding], Keyword.t()) :: Macro.t() + def compile!(env, bindings, options) when is_list(bindings), + do: Enum.map(bindings, &compile!(env, &1, options)) + + def compile!(env, {name, arity}, options), + do: compile!(env, {name, arity, options[:handler]}, options) + + def compile!(env, {name, [head | _] = arity, handler}, options) + when is_integer(head) do + arity + |> Enum.map(&{name, &1, handler}) + |> Enum.map(&compile!(env, &1, options)) + end + + def compile!(env, {name, arity, handler}, options) do + {enum, length, generator} = + if is_list(arity) do + {arity, length(arity), &Macro.var(&1, env.module)} + else + {0..(arity && arity - 1), arity, &Macro.var(:"arg#{&1}", env.module)} + end + + params = Enum.map(enum, generator) + + result = quote do: apply(unquote(env.module), unquote(name), unquote(params)) + + handle = + case handler do + func when is_atom(func) and not is_nil(func) -> + quote do: unquote(func)(unquote(result)) + + {mod, func} -> + quote do: apply(unquote(mod), unquote(func), [unquote(result)]) + + _fail -> + raise CompileError, + description: "Invalid handler definition for #{name}/#{length}", + file: env.file, + line: env.line + end + + ex_docs = + if options[:docs] do + quote do: @doc("Unsafe proxy definition for `#{unquote(name)}/#{unquote(length)}`.") + else + quote do: @doc(false) + end + + quote do + unquote(ex_docs) + + def unquote(:"#{name}!")(unquote_splicing(params)) do + unquote(handle) + end + end + end + + def compile!(env, _invalid, _options), + do: + raise(CompileError, + description: "Invalid function reference provided", + file: env.file, + line: env.line + ) +end From fc4d90b6641148969efef901bffaa0b4e1494b29 Mon Sep 17 00:00:00 2001 From: Benjamin Danklin <34633373+bdanklin@users.noreply.github.com> Date: Mon, 19 Jun 2023 10:37:35 +1000 Subject: [PATCH 2/3] show docs --- lib/nostrum/api.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 3ac41a299..1de764600 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -66,7 +66,8 @@ defmodule Nostrum.Api do alias Nostrum.Shard.{Session, Supervisor} use Nostrum.UnsafeHelpers, - handler: :bangify + handler: :bangify, + docs: true @typedoc """ Represents a failed response from the API. From d88602bdf4205b06d1161285b671029dd9bded3a Mon Sep 17 00:00:00 2001 From: Benjamin Danklin <34633373+bdanklin@users.noreply.github.com> Date: Mon, 19 Jun 2023 10:38:16 +1000 Subject: [PATCH 3/3] correct function name --- lib/nostrum/api.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 1de764600..72323a9c4 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -166,7 +166,7 @@ defmodule Nostrum.Api do - `type` - The type of status to show. 0 (Playing) | 1 (Streaming) | 2 (Listening) | 3 (Watching) - `stream` - URL of twitch.tv stream """ - @unsafe {:update_status, [:pid, :status, :game, :type, :stream]} + @unsafe {:update_shard_status, [:pid, :status, :game, :type, :stream]} @spec update_shard_status(pid, status, String.t(), integer, String.t() | nil) :: :ok def update_shard_status(pid, status, game, type \\ 0, stream \\ nil) do Session.update_status(pid, to_string(status), game, stream, type)