Dialyzer confusion over equal opaque and transparent types
Kostis Sagonas
kostis@REDACTED
Wed Feb 26 00:31:13 CET 2020
On 2/25/20 11:12 AM, Dániel Szoboszlay wrote:
> Hi,
>
> I have a small example where Dialyzer gives a very weird warning because
> of some confusion over equal opaque and transparent types. Is this a bug
> in Dialyzer or is there actually some type error here that my naked eyes
> cannot see?
>
> -module(foo).
>
> -record(f, {f}).
> -type int() :: atom().
> -opaque ext() :: int().
> -opaque f() :: #f{f :: int()}.
>
> -export([f/1]).
> -export_type([f/0, ext/0]).
>
> -spec f(f()) -> ok.
> f(#f{f = F}) ->
> x(F).
>
> -spec x(ext()) -> ok.
> x(_) ->
> ok.
>
>
> This module produces no Dialyzer warnings. However, a different module
> using its API does: ... <SNIP>
>
> The problem is actually caused by foo:x/1's type specification: it uses
> the opaque ext() type instead of the transparent int().
Right. Since you have figured out what the problem is, why don't you
use some solution along those lines?
I.e., either declare x/1 as taking an int(), because this is what the
module-local x/1 function is called from the body of f/1, or, even
simpler, simply remove the -spec for the x/1 function, so that you do
not confuse dialyzer's type inferencer.
Note that x/1 is a module-local function, so there is really no good
reason to declare a spec for it, let alone declare that it is called
with some opaque term.
Either of the above two actions will actually work and you will avoid
the (admittedly confusing) warning.
> ... The two types [ext() and int()] are declared equal,
Not really; see below.
> so I'd assume they can be used interchangeably
> within the foo module. The fact that ext() is opaque should only matter
> in other modules. Yet, this construct somehow confuses Dialyzer.
You are probably missing a very subtle difference between -type and
-opaque declarations. (This is not your fault, because I think that the
reference manual does not explain this very well.) Opaque declarations
are actually of the form TypeName :: TermStructure, i.e., the right hand
side declares the structure of the opaque terms of that type name.
They do not declare type aliases (as e.g., the -type int() :: atom().
declaration does). This is the reason why Brujo's suggestion
>> Another way to make it work is to define your types like this…
>>
>> -opaque ext() :: atom().
>> -type int() :: ext().
>> -opaque f() :: #f{f :: int()}.
also stops confusing Dialyzer. (By the way, this solution has nothing
to do with "reversing any subtype" as written in that discussion.)
> To give some context for the curious: ... <SNIP> ...
<SNIP> because curiosity killed the cat :-)
Kostis
More information about the erlang-questions
mailing list