Module-local typespec primitives.
Two responsibilities:
index/1— build a{name, arity} => :public | :privatemap of the@type/@typep/@opaquedeclarations in a module's body, used to recognize local type references when rewriting extracted specs.qualify/3— walk an@spec/@callback/@macrocallbackattribute AST and rewrite every bare reference to a local type into a fully-qualified call against the source module. So@spec foo() :: t()insideMyApp.Sourcebecomes@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 walkingaandbtoo.id()inside@spec fetch(id())→Source.id(). The function name (fetch) is preserved because the rewriter is structurally aware of@spec/@callbackshape.
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. @spechead name (function being specced).- Anything outside
@spec/@callback/@macrocallback(e.g.@doc,@typedoc, function bodies — seequalify/3clauses).
@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.
Summary
Functions
Build a type index from a list of top-level module body nodes.
Qualify a typespec attribute against source_module.
Does this typespec AST reference any local type in type_index?
Types
@type type_index() :: %{required({atom(), non_neg_integer()}) => :public | :private}
Functions
@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.
@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.
@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.