EXPERIMENTAL backend. It reads signatures inferred by Elixir's set-theoretic
type system. The source is the ExCk BEAM chunk, not user-written @spec.
Not the recommended path. Use RpcElixir.Types.FromSpec for real work. This
module tracks the type system as it evolves toward a public API.
Hard caveats:
Requires Elixir 1.19 or later. Set-theoretic signatures do not exist before that, so every lookup returns
{:error, :no_signature}on older versions. The library itself supports Elixir 1.17+, but this backend does not. Its own tests carry the:requires_inferencetag and are excluded below 1.19 (seetest/test_helper.exs).Private API. The
ExCkchunk format andModule.Types.Descrshape are undocumented compiler internals. The chunk version is:elixir_checker_v3on Elixir 1.19. Both have changed every minor release, so expect breakage on upgrade.Requires
Code.compiler_options(infer_signatures: true)when the target module compiles. Without it the chunk carries only function names. Enable it in your ownmix.exs:defmodule MyApp.MixProject do use Mix.Project Code.compiler_options(infer_signatures: true) def project, do: [...] endInference is lossy. Most argument types come back as
dynamic. You get more only when the function pattern-matches or guards on input. Returns fare better.Anything untranslatable becomes
%{kind: "dynamic"}, not a raise. So callers can fall back toFromSpec.
Gaps versus FromSpec
| Area | Behaviour |
|---|---|
| Arguments | usually dynamic, unless the function pattern-matches or guards on them |
| Lists | collapse to dynamic; no list kind |
T | nil | nullability is dropped |
| Optional map keys | map openness is ignored, so all fields look required |
| Module identity | lost: no :struct tag, no built-in date or decimal resolution, no Ecto schema derivation, and wire_spec/0 is never consulted |
any() / term() | become dynamic instead of raising |
integer() | float() | widened to primitive / float |
| Multi-clause functions | only the first inferred clause survives |
{:error, E} branches | unprovable unless the body returns one, so error comes back as nil |
What it does recover: atom-literal enums (:a | :b) become enum. An RPC
return is decomposed when it contains an {:ok, T} tuple.
Summary
Functions
Convenience for the RPC convention call(input, context) :: {:ok, output}.
Returns the inferred argument and return type maps for an MFA. Gives {:error, :no_signature} when unavailable.
Functions
@spec fetch_rpc(module(), atom()) :: {:ok, %{ input: RpcElixir.Types.internal_spec(), output: RpcElixir.Types.internal_spec(), error: RpcElixir.Types.internal_spec() | nil }} | {:error, :no_signature} | {:error, {:invalid_return, RpcElixir.Types.internal_spec()}}
Convenience for the RPC convention call(input, context) :: {:ok, output}.
Inference cannot prove an {:error, _} branch unless the body returns one.
So the recovered shape is usually a single {:ok, T} tuple. Returns
{:ok, %{input: t, output: t, error: t | nil}} when that pattern is
recognized, else {:error, {:invalid_return, t}}.
@spec fetch_signature(module(), atom(), non_neg_integer()) :: {:ok, %{ args: [RpcElixir.Types.internal_spec()], return: RpcElixir.Types.internal_spec() }} | {:error, :no_signature}
Returns the inferred argument and return type maps for an MFA. Gives {:error, :no_signature} when unavailable.