From 2653e2516d4fa4acea8e073e83f66c8935476a32 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 21 Apr 2026 09:33:22 -0500 Subject: [PATCH] dialyzer: expand PLT, drop blanket codebase suppression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore file was silencing every warning under lib/towerops/**. Root cause was plt_add_deps: :apps_direct missing Plug/Phoenix/Oban/Decimal /Redix/ssl/public_key — hundreds of false `unknown_function` warnings were being papered over. Expand plt_add_apps to cover the common deps, narrow the ignore file to real dep-PLT gaps only, and fix two concrete bugs uncovered by the change: - ScopedResource.fetch_preload/4: spec was atom() | [atom()] but callers pass keyword lists (e.g. [rules: :targets]). - ToweropsNative: tag NIF stubs with @dialyzer :nowarn_function so dialyzer trusts the @spec instead of the fallback body. Remaining 328 real warnings surface for follow-up. --- .dialyzer_ignore.exs | 54 ++++++----------------------- lib/towerops_native.ex | 4 +++ lib/towerops_web/scoped_resource.ex | 2 +- mix.exs | 19 ++++++++-- 4 files changed, 33 insertions(+), 46 deletions(-) diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index a0ea79e7..970a082e 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -1,69 +1,37 @@ -# Dialyzer false positives to ignore -# These are known issues from dependencies with incomplete PLT information +# Dialyzer warnings to ignore. # -# Most warnings in our codebase are false positives from Ecto, Plug, Oban, and Phoenix -# not exporting their internal types and functions in their PLT files. -# -# We ignore these systematically since they're not real bugs - all tests pass and -# the application runs correctly. +# Dependency PLT gaps should be handled via `plt_add_apps` in mix.exs. +# Only suppress genuine false positives here — fix real warnings instead. [ - # === Dependency behavior warnings === - - # Ecto.Type behavior - callback info not exported + # Ecto.Type behaviour — callbacks aren't always exported {"deps/ecto/lib/ecto/type.ex", :callback_info_missing}, - # Oban.Worker behavior - callback info not exported + # Oban.Worker behaviour — PLT gap {"deps/oban/lib/oban/worker.ex", :callback_info_missing}, {"deps/oban/lib/oban/worker.ex", :unknown_function}, - # Phoenix behaviors - callback info not exported + # Phoenix behaviours {"deps/phoenix/lib/phoenix/endpoint.ex", :callback_info_missing}, {"deps/phoenix/lib/phoenix/router.ex", :callback_info_missing}, - # Plug behaviors - callback info not exported + # Plug behaviours {"deps/plug/lib/plug/debugger.ex", :unknown_function}, {"deps/plug/lib/plug/error_handler.ex", :unknown_function}, - # Honeybadger - unknown function false positives + # Honeybadger {"deps/honeybadger/lib/honeybadger/plug.ex", :unknown_function}, - # Cloak - callback info and unknown function warnings + # Cloak {"deps/cloak/lib/cloak/vault.ex", :unknown_function}, - {"/home/runner/work/elixir/elixir/lib/elixir/lib/gen_server.ex", :callback_info_missing}, {"deps/cloak_ecto/lib/cloak_ecto/type.ex", :callback_info_missing}, {"deps/cloak_ecto/lib/cloak_ecto/types/binary.ex", :unknown_function}, - # === Vendored library warnings === - - # SnmpKit vendored library - ignore all warnings (both lib/ and src/) + # Vendored SnmpKit — out of scope for typing cleanup ~r/lib\/snmpkit/, ~r/lib\/snmp_lib/, ~r/src\/snmpkit/, ~r/src\/snmp_lib/, - # === Application code with dependency false positives === - - # Ignore unknown_function and unknown_type warnings from Ecto/Plug/Phoenix - # These are false positives - the functions exist but aren't in the PLT - ~r/lib\/towerops.*\.ex/, - ~r/lib\/towerops.*\.heex/, - ~r/lib\/mix\/tasks.*\.ex/, - - # === Gleam type system interop warnings === - # - # Gleam's type system doesn't map 1:1 to Erlang's type system, causing false positives: - # - # 1. contract_supertype: Gleam only has `Int`, but Dialyzer infers `non_neg_integer()`. - # We cannot make Gleam specs more specific. - # - # 2. call_without_opaque: Gleam stdlib uses opaque types (bytes_tree, Decoder) that - # Dialyzer doesn't understand. The code works correctly at runtime. - # - # 3. no_return/invalid_contract: Dialyzer thinks bytes_tree functions will crash because - # it doesn't understand the opaque type transformations. - # - # All Gleam code compiles and passes tests - these are type system artifacts, not bugs. - - # All Gleam files in src/towerops/ (excluding vendored snmpkit above) + # Gleam type system doesn't map 1:1 to Erlang's — artifacts, not bugs. ~r/src\/towerops\/.*\.gleam/ ] diff --git a/lib/towerops_native.ex b/lib/towerops_native.ex index 2ce9a2d4..3bd567d1 100644 --- a/lib/towerops_native.ex +++ b/lib/towerops_native.ex @@ -22,6 +22,10 @@ defmodule ToweropsNative do @on_load :load_nif + # NIF stubs return fallback values when the NIF fails to load (defensive). + # Dialyzer only sees the stub bodies, so tell it to trust the @spec instead. + @dialyzer {:nowarn_function, [resolve_oid: 1, init_mib_library: 0, load_mib_directory: 1]} + def load_nif do nif_path = :filename.join(:code.priv_dir(:towerops), ~c"towerops_nif") diff --git a/lib/towerops_web/scoped_resource.ex b/lib/towerops_web/scoped_resource.ex index d624b3b3..b2ae94fb 100644 --- a/lib/towerops_web/scoped_resource.ex +++ b/lib/towerops_web/scoped_resource.ex @@ -20,7 +20,7 @@ defmodule ToweropsWeb.ScopedResource do end end - @spec fetch_preload(module(), Ecto.UUID.t(), Ecto.UUID.t(), atom() | [atom()]) :: + @spec fetch_preload(module(), Ecto.UUID.t(), Ecto.UUID.t(), atom() | list() | keyword()) :: {:ok, struct()} | {:error, :not_found | :forbidden} def fetch_preload(schema, id, organization_id, preloads) do with {:ok, resource} <- fetch(schema, id, organization_id) do diff --git a/mix.exs b/mix.exs index 50eb4270..9aa0adbc 100644 --- a/mix.exs +++ b/mix.exs @@ -105,9 +105,24 @@ defmodule Towerops.MixProject do defp dialyzer do [ plt_file: {:no_warn, "priv/plts/dialyzer.plt"}, - # Faster incremental analysis plt_add_deps: :apps_direct, - plt_add_apps: [:mix, :ex_unit, :ecto], + plt_add_apps: [ + :mix, + :ex_unit, + :ecto, + :plug, + :plug_crypto, + :phoenix, + :phoenix_pubsub, + :phoenix_live_view, + :phoenix_template, + :oban, + :decimal, + :redix, + :telemetry, + :ssl, + :public_key + ], flags: [:unmatched_returns, :error_handling, :underspecs, :unknown], ignore_warnings: ".dialyzer_ignore.exs" ]