Documentation/Buki/Dialyzer/ skills /dialyzer-analysis

📖 dialyzer-analysis

Use when analyzing and fixing Dialyzer warnings and type discrepancies in Erlang/Elixir code.



Overview

Understanding and fixing Dialyzer warnings in Erlang and Elixir code.

Type Specifications

Basic Specs

@spec add(integer(), integer()) :: integer()
def add(a, b), do: a + b

@spec get_user(pos_integer()) :: {:ok, User.t()} | {:error, atom()}
def get_user(id) do
  # implementation
end

Complex Types

@type user :: %{
  id: pos_integer(),
  name: String.t(),
  email: String.t(),
  role: :admin | :user | :guest
}

@spec process_users([user()]) :: {:ok, [user()]} | {:error, String.t()}

Generic Types

@spec map_values(map(), (any() -> any())) :: map()
@spec filter_list([t], (t -> boolean())) :: [t] when t: any()

Common Warnings

Pattern Match Coverage

# Warning: pattern match is not exhaustive
case value do
  :ok -> :success
  # Missing :error case
end

# Fixed
case value do
  :ok -> :success
  :error -> :failure
  _ -> :unknown
end

No Return

# Warning: function has no local return
def always_raises do
  raise "error"
end

# Fixed with spec
@spec always_raises :: no_return()
def always_raises do
  raise "error"
end

Unmatched Returns

# Warning: unmatched return
def process do
  {:error, "failed"}  # Return value not used
  :ok
end

# Fixed
def process do
  case do_something() do
    {:error, reason} -> handle_error(reason)
    :ok -> :ok
  end
end

Unknown Functions

# Warning: unknown function
SomeModule.undefined_function()

# Fixed: ensure function exists or handle dynamically
if Code.ensure_loaded?(SomeModule) and
   function_exported?(SomeModule, :function_name, 1) do
  SomeModule.function_name(arg)
end

Type Analysis Patterns

Union Types

@type result :: :ok | {:ok, any()} | {:error, String.t()}

@spec handle_result(result()) :: any()
def handle_result(:ok), do: nil
def handle_result({:ok, value}), do: value
def handle_result({:error, msg}), do: Logger.error(msg)

Opaque Types

@opaque internal_state :: %{data: map(), timestamp: integer()}

@spec new() :: internal_state()
def new, do: %{data: %{}, timestamp: System.system_time()}

Remote Types

@spec process_conn(Plug.Conn.t()) :: Plug.Conn.t()
@spec format_date(Date.t()) :: String.t()

Success Typing

Dialyzer uses success typing:

  • Approximates what a function can succeed with
  • Different from traditional type systems
  • May miss some errors, but no false positives (in theory)

Example

# Dialyzer infers: integer() -> integer()
def double(x), do: x * 2

# More specific spec
@spec double(pos_integer()) :: pos_integer()
def double(x) when x > 0, do: x * 2

Best Practices

  1. Start with Core Modules: Add specs to public APIs first
  2. Use Strict Types: Prefer specific types over any()
  3. Document Assumptions: Use specs to document expected behavior
  4. Test Specs: Ensure specs match actual behavior
  5. Iterative Fixing: Fix warnings incrementally

Debugging Tips

Verbose Output

mix dialyzer --format dialyzer

Explain Warnings

mix dialyzer --explain

Check Specific Files

mix dialyzer lib/my_module.ex