# `Adze.Types`
[🔗](https://github.com/matthewlehner/adze/blob/v0.1.0/lib/adze/types.ex#L1)

Module-local typespec primitives.

Two responsibilities:

  * `index/1` — build a `{name, arity} => :public | :private` map of the
    `@type` / `@typep` / `@opaque` declarations in a module's body, used
    to recognize local type references when rewriting extracted specs.

  * `qualify/3` — walk an `@spec` / `@callback` / `@macrocallback`
    attribute AST and rewrite every bare reference to a local type
    into a fully-qualified call against the source module. So
    `@spec foo() :: t()` inside `MyApp.Source` becomes
    `@spec foo() :: MyApp.Source.t()` when extracted.

Built specifically for `Adze.Extract`: when a public def whose `@spec`
references a module-local type gets pulled into a new module, the
qualified form keeps the type defined in exactly one place (the
original source) and the new module simply references it.

## What's rewritten

  * Bare `t/0` → `Source.t/0`.
  * `result(a, b)` → `Source.result(a, b)`, recursively walking `a` and
    `b` too.
  * `id()` inside `@spec fetch(id())` → `Source.id()`. The function
    name (`fetch`) is preserved because the rewriter is
    structurally aware of `@spec`/`@callback` shape.

## What's left alone

  * `String.t()` and any other already-qualified call.
  * Built-in types (`integer`, `term`, `pos_integer`, etc.) — they're
    not in the index so the qualification check misses cleanly.
  * `@spec` head name (function being specced).
  * Anything outside `@spec` / `@callback` / `@macrocallback` (e.g.
    `@doc`, `@typedoc`, function bodies — see `qualify/3` clauses).

## `@typep` references

An extracted `@spec` cannot reference a `@typep` from another module —
the type is module-private. We surface this by throwing
`{:typep_referenced, %{type: {name, arity}, source: module}}`, which
`Adze.Extract` catches and returns as `{:error, ...}`. The user's
remedy: widen the `@typep` to `@type` in source first, then re-extract.

# `type_index`

```elixir
@type type_index() :: %{required({atom(), non_neg_integer()}) =&gt; :public | :private}
```

# `index`

```elixir
@spec index([Macro.t()]) :: type_index()
```

Build a type index from a list of top-level module body nodes.

Walks the body for `@type` / `@typep` / `@opaque` declarations.
Returns `%{}` if there are none.

# `qualify`

```elixir
@spec qualify(Macro.t(), type_index(), String.t()) :: Macro.t()
```

Qualify a typespec attribute against `source_module`.

Rewrites `@spec` / `@callback` / `@macrocallback` ASTs so every local
type reference is replaced with a fully-qualified call against
`source_module`. Other attributes (`@doc`, `@typedoc`, etc.) pass
through unchanged.

May throw `{:typep_referenced, info}` if the typespec references a
`@typep` from the source module — callers should catch and convert
to an error.

# `references_local?`

```elixir
@spec references_local?(Macro.t(), type_index()) :: boolean()
```

Does this typespec AST reference any local type in `type_index`?

Cheap pre-check used by `Adze.Extract` to decide whether a closure
definition needs the AST-rewrite path or can stay on the
slice-from-source path.

---

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