透析器遗漏类型说明错误

Dialyzer misses error with type specification

下面的Erlang代码在类型说明上似乎有明显的错误,但是dialyzer说一切正常。我是误会了还是这是透析器中的错误? 运行 在 Erlang 19.3 上

-module(foobar).

-export([foo/1]).

-spec foo(atom()) -> ok | {error, atom()}.
foo(Arg) -> bar(Arg).

-spec bar(atom()) -> ok | error.
bar(baz) -> error;
bar(_) -> ok.

首先是一个简短的回答,使用 Dialyzer 的格言:

  1. Dialyzer 永远不会错。(Erlang 程序员经常背诵)
  2. Dialyzer 从未承诺会发现您代码中的所有错误。(不太出名)

最大数字 2 是(诚然不是很令人满意)"standard" 对任何 "Why Dialyzer didn't catch this error" 问题的回答。


更具解释性的答案:

Dialyzer 对 return 函数值的分析经常进行 over-approximations。因此,类型中包含的任何值都被视为 "maybe returned" 值。不幸的是 side-effect 有时肯定会被 returned 的值(比如你的 error 原子)也被认为是 "maybe returned"。 Dialyzer 必须保证 maxim 1(永远不会错),所以在意外值 "may be returned" 的情况下,它不会发出警告(在 foo 的规范中),除非 none 实际上可以 return 编辑指定的值。最后一部分在整个函数的级别进行检查,并且由于在您的示例中确实有一些子句 return ok,因此不会生成任何警告。


最后,如果您希望 Dialyzer 对规格非常严格,您可以使用 -Wunderspecs-Woverspecs-Wspec_diffs(请参阅文档了解它们各自的作用)

方案 1:

If there is any path through your code that does not match your specified types, then dialyzer will report an error.

方案 2:

If there is any path through your code that does match your specified types, then dialyzer will not report an error.

diaylyzer 在制度 2 下运作。在您的情况下,如果您致电 foo(hello):

1> c(foobar).
{ok,foobar}

2> foobar:foo(hello).
ok

3> 

...然后使用所需的参数类型 atom() 调用了 foo(),并且 foo() 返回了所需类型之一,ok,因此透析器不报告一个错误。

Remember, Dialyzer is optimistic. It has figurative faith in your code, and because there is the possibility that the function call to [foo] succeeds ..., Dialyzer will keep silent. No type error is reported in this case.

http://learnyousomeerlang.com/dialyzer

Dialyzer 可能比您的示例更令人困惑,例如:

-module(my).
-export([test/0, myand/2]).
%-compile(export_all).
-include_lib("eunit/include/eunit.hrl").

test() ->
    myand({a,b}, [1,2]).

myand(true, true) -> true;
myand(false, _) -> false;
myand(_, false) -> false.
  1. 你能找出代码中的错误吗?
  2. 透析器会发现错误吗?花点时间尝试确定您可以推断出关于 myand() 的参数类型的哪些信息。

答案:myand() 的第一个参数必须是 boolean()...嗯,这实际上不是真的——看看 myand() 的最后一个子句。第一个参数也可以是任何东西。三个函数子句告诉我们第一个参数的所有可能值是:true、false 或任何值。包含所有三种可能性的类型是 any()。然后 dialyzer 查看第二个参数,dialyzer 对第二个参数的类型得出相同的结论。因此,透析器将 myand() 的类型推断为:

myand(any(), any()) -> boolean().

...这意味着在 dialyzer 看来调用 myand({a,b}, [1,2]) 不是错误。啊??相反,我的羽毛朋友:

1> c(my).  
{ok,my}

2> my:test().
** exception error: no function clause matching my:myand({a,b},[1,2]) (my.erl, line 9)

3> 

显然,myand() 代码的目的是 myand() 应该至少需要一个 boolean() 参数——但显然 dialyzer 会单独收集每个变量的信息:

+---------------------------------------+
|          1st arg info                 |
|                                       |               
|   info1     true                      |                          
|   info2     false                     |
|   info3     any                       |
|           ---------                   |
|             any() -- inferred type    |
|                                       |
+---------------------------------------+

+---------------------------------------+
|          2nd arg info                 |
|                                       |    
|   info1     true                      |
|   info2     any                       |
|   info3     false                     |
|            -------                    |
|             any() -- inferred type    |
|                                       |
+---------------------------------------+

因此,test()/myand() 代码是透析器无法报告代码中实际错误的情况。

有一些方法可以帮助透析器发现错误:

1) 枚举函数子句中所有可能的参数:

myand(true, true) -> true;
myand(false, true) -> false;
myand(true, false) -> false.

"Programming Erlang" 页。 152 警告不要在使用透析器时使用 _ 作为参数。

2)或者,如果枚举的情况太多,可以使用guards来指定参数类型:

myand(true, true) -> true;
myand(false, _Y) when is_boolean(_Y) -> false;
myand(_X, false) when is_boolean(_X) -> false.

3) 当然,您可以使用类型说明:

 -spec myand(boolean(), boolean()) -> boolean().