Време е за нова история на Erlang, деца! Това е за един от любимите ми инструменти на Erlang: диализатор.

Може би вече знаете това, но винаги препоръчвам да проверявате целия си Erlang код с диализатор веднага щом го напишете. Можете да ме чуете да проповядвам точно това нещо в това интервю с Functional Geekery. Толкова обичаме тези инструменти (като диализатор, xref и elvis) в Inaka, че дори създадохме „Meta Test Suites“, които можете да използвате, за да стартирате диализатор като част от вашите общи тестови пакети всеки път.

И така започва тази история…

Работещ диализатор

Dialyzer е едновременно инструмент за обвивка, който можете да стартирате на вашата конзола и, разбира се, en Erlang приложение, което можете да стартирате от вашите Erlang VM. За набора от мета тестове на Katana-Test трябваше да стартираме диализатор от код, както може би очаквахте.

По-долу е показана опростена версия на това, което трябваше да направим...

Този модул съдържа 2 основни функции: plt/0 и run/1. И двамата в крайна сметка извикват dialyzer:run/1 с различни опции. Основната разлика е analysis_type: Докато plt/0 се използва за изграждане на plt, от който диализаторът се нуждае, за да анализира вашия код, се използва run/1 за действителна проверка на успешните типове във вашите модули и функции.

dialyzer:run/1 връща списък с предупреждения във формуляр Erlang (повече за това по-долу), които по-късно можете да изпълните през dialyzer:format_warning/1,2, за да получите правилни съобщения във формат string().

Дотук добре. Сега, нека да видим какво се случва, когато се опитаме да анализираме самия модул, както се опитахме да направим с нашия мета пакет...

1> c(dia, [debug_info]).
{ok,dia}
2> dia:plt().
  Creating PLT dia.plt ... done in 0m11.30s
ok
3> dia:run(["dia.beam"]).
  Checking whether the PLT dia.plt is up-to-date... yes
  Proceeding with analysis... done in 0m0.15s
dia.erl:28: The call dialyzer:format_warning(Warning::{atom(),{_,_},{_,_}}) breaks the contract (raw_warning()) -> string()
ok
4>

О, човече! Разваляме договор!

Какво става тук?

Dialyzer ни казва, че използваме неподходящ параметър при извикване на dialyzer:format_warning/1. Тази функция очаква raw_warning()и ние предаваме кортеж като неин параметър. Откъде идва този кортеж? Това всъщност е един от елементите, които получаваме в резултата от dialyzer:run/1. Така че нека проверим тази „спецификация на функцията“:

-spec run(dial_options()) -> [dial_warning()].

И така, да... run/1 връща списък с dial_warning(). Но format_warning/1 очаква вместо това raw_warning(). Какво можем да направим? Ами… проверете спецификацията на format_warning/2:

-spec format_warning(raw_warning() | dial_warning(), fopt()) -> string().

Тогава можем напълно да използваме тази функция! Просто трябва да намерим fopt(), който можем да използваме. Какво изобщо е fopt()? "Виж това!"

-type fopt() :: 'basename' | 'fullpath'.

По същество се определя дали пътищата, отпечатани в съобщенията, трябва да бъдат абсолютни или относителни. За да получим желаните резултати, можем напълно да използваме ‘basename’ като втори параметър и да приключим с него...

Но… изчакайте малко… не прави ли точно това format_warning/1?

format_warning(W) ->
  format_warning(W, basename).

И така... защо format_warning/1 не приема dial_warning() като свой параметър? Вероятно защото синът на обущаря винаги ходи бос, т.е. спецификациите на модула Erlang, който проверява спецификациите на модулите Erlang, са грешни ;)

...и сега имате PR, за да поправите това :)