# `Mutare.Calls`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.1/lib/mutare/calls.ex#L1)

Call-resolution readers for custom mutators and macro integrations.

`resolved_call/1` normalizes qualified, aliased, imported, and Erlang-module calls to `{module, function, arguments, rebuild}`. Use `rebuild` to preserve the source's written call form when that is compile-safe; bare imported calls may be requalified when the replacement changes name or arity. It operates on nodes passed to a mutator by Mutare's transform.

This reads the `alias`/`import` stamps the transform places on the AST before mutators run, so it is only meaningful on a node handed to a mutator by the transform (a `mutate/1` argument) — exactly where a call-matching mutator needs it.

`module_key/1` encodes a real module atom into the key shape `resolved_call/1` returns, and `resolved_call_to/3` bundles the common "is this a call to module M (function F)?" match — together they save a caller from ever constructing or pattern-building the key representation itself.

`resolved_routed_call/1` is the routed-call twin — a call matched by a `call_routes` entry, macro or function. It returns a stable `Mutare.CallRouting.Call` with a natural module atom, visible arguments, pipe information, and a source-preserving rebuild function.

`routed_treatments/1` reads *how a node's macro is registered* — the resolved per-argument routing the merged registry (built-ins + every mutator's/extension's `call_routes/0` + the declarative `:call_routes` option) assigned it. A macro host uses it in two places: on its own call's `node`, to locate the positions the route marked `:hosted` (including values nested under `{:keyword, …}`); and on a nested macro inside a fragment it walks, to read its argument treatments or determine whether the whole call is skipped (`:skip`). In both cases it replaces re-deriving the classification.

## Example

    defmodule MyApp.Mutators.Upcase do
      @behaviour Mutare.Mutator
      def name, do: :upcase_swap

      def mutate(node) do
        case Mutare.Calls.resolved_call(node) do
          {[:String], :upcase, [arg], rebuild} -> [rebuild.(:downcase, [arg])]
          _ -> :skip
        end
      end
    end

# `module_key`

```elixir
@type module_key() :: Mutare.Transform.Calls.module_key()
```

A resolved module: an Elixir-module path (`[:Enum]`, `[:String]`) or an Erlang-module atom
(`:binary`, `:string`). A mutator keys its table on the function's module-key representation.

# `module_key`

```elixir
@spec module_key(module()) :: module_key()
```

The resolved-call key for a module atom — the shape `resolved_call/1` returns in its
first element. An Elixir module becomes its segment path, an Erlang module stays an
atom. Use it to compare a configured module against resolved calls instead of
re-deriving the encoding.

    iex> Mutare.Calls.module_key(Ecto.Query)
    [:Ecto, :Query]
    iex> Mutare.Calls.module_key(:binary)
    :binary

# `resolved_call`

```elixir
@spec resolved_call(Macro.t()) ::
  {module_key(), atom(), [Macro.t()], (atom(), [Macro.t()] -&gt; Macro.t())} | nil
```

Returns `{module, function, arguments, rebuild}` for a resolved standard-library
call, or `nil`.

`module` is an Elixir alias path such as `[:String]` or an Erlang module atom.
`rebuild.(new_function, new_arguments)` preserves the call's written qualifier when safe.
Remote calls keep their written qualifier or alias. Bare imported calls stay bare for
value-only replacements, but may be requalified when the replacement changes name or arity.

    iex> node = Sourceror.parse_string!("String.upcase(s)")
    iex> {module, function, arguments, rebuild} =
    ...>   Mutare.Calls.resolved_call(node)
    iex> {module, function}
    {[:String], :upcase}
    iex> Sourceror.to_string(rebuild.(:downcase, arguments))
    "String.downcase(s)"

    iex> erlang = Sourceror.parse_string!(":binary.first(b)")
    iex> {module, function, _arguments, _rebuild} =
    ...>   Mutare.Calls.resolved_call(erlang)
    iex> {module, function}
    {:binary, :first}

    iex> Mutare.Calls.resolved_call(Sourceror.parse_string!("foo(x)"))
    nil

# `resolved_call_to`

```elixir
@spec resolved_call_to(Macro.t(), module() | module_key(), atom() | [atom()] | :any) ::
  {:ok, atom(), [Macro.t()], (atom(), [Macro.t()] -&gt; Macro.t())} | :error
```

Matches a resolved call against a target module and function name(s).

`module` may be a real module atom or an already-encoded `t:module_key/0`; `functions`
is one name, a list of names, or `:any` (the default). Returns
`{:ok, function, arguments, rebuild}` on a match, `:error` otherwise — including for a
node that is not a resolved call at all. The common call-matching preamble without
hand-building the key:

    iex> node = Sourceror.parse_string!("String.upcase(s)")
    iex> {:ok, fun, args, rebuild} = Mutare.Calls.resolved_call_to(node, String)
    iex> fun
    :upcase
    iex> Sourceror.to_string(rebuild.(:downcase, args))
    "String.downcase(s)"

    iex> node = Sourceror.parse_string!("String.upcase(s)")
    iex> Mutare.Calls.resolved_call_to(node, String, [:downcase, :capitalize])
    :error
    iex> Mutare.Calls.resolved_call_to(node, Enum)
    :error

# `resolved_routed_call`

```elixir
@spec resolved_routed_call(Macro.t()) :: Mutare.CallRouting.Call.t() | nil
```

Return the stable call value for a node the call-route resolver matched, or `nil` for any
other node. Extension callbacks receive this value directly; the reader remains useful to a
host walking nested macro nodes.

# `routed_treatments`

```elixir
@spec routed_treatments(Macro.t()) ::
  [Mutare.CallRouting.routing_treatment()] | :skip | nil
```

Returns the resolved treatment for each visible argument of a registered macro
call, or `nil`.

Treatments come from the fully merged macro-routing registry and include any
shape-aware classification already performed for the call. The result may contain
static treatments, `:hosted`, or nested keyword routing.

Inside `c:Mutare.Mutator.MacroHost.host/2`, calling this on the received call's `node` returns
the treatments that granted hosting, so a host locates its `:hosted` positions without
re-classifying the call.

A keyed refinement reads back in the author form it was written in (`[:expression, timeout: :raw]`).
For a piped call, the left side of the pipe is not included. A call routed `:skip` returns the
bare `:skip`; a call that has no registered route returns `nil`.

## Routing describes arguments; registration identifies ownership

A route specifies how to treat a registered macro's *arguments*. It does not stop a mutator's own
catalog from matching the **call**, which the expression walk still offers — so a macro
registered `:raw` is opaque in its interior and exposed in its name. (The call-level `:skip`
is the exception: an inert leaf is offered to nobody, and this reader returns `:skip` for it.)

That matters for a catalog keyed on a bare function-name atom — all there is to match on
inside a DSL whose API functions are never imported. A name is not an identity: rewriting
somebody else's macro to a sibling name emits a call nobody defines, which the library rejects
while expanding. Having a treatment is exactly what being registered means, so a non-`nil`
result identifies a registered macro — skip the node:

    def mutate(node) do
      if Mutare.Calls.routed_treatments(node), do: :skip, else: swap(node)
    end

Test for `nil`, not for a non-empty list: `[]` is a registered macro with no visible
arguments, whose treatment is also defined by the registered route. The test recognises a *registered* macro —
including one registered by name only, the route for calls whose module cannot be resolved —
and nothing beyond that. An unregistered macro is indistinguishable from an ordinary call, so
a catalog still needs whatever arity and import gating it already applies.

A catalog that matches through `resolved_call/1` needs no such test: it keys on
`{module, function}` and gets `nil` for a call it cannot resolve.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
