Gradualizer
Gradualizer copied to clipboard
dialyzer source code module attribute support
Hello! I would like to discuss possibility of dialyzer source code module attribute support in gradualizer. The reason for this are some places, for example in elixir core, like this:
https://github.com/elixir-lang/elixir/blob/a6da9e59b73877b60b97957742552f904b2ced1b/lib/elixir/lib/protocol.ex#L704-L706
which explicitly says us - hey, we know this place is not safe, and we know what we are doing. Of course this is compromise, but probably this make sense? At the moment gradualizer says
The pattern #{'__struct__' := __@1} on line 1 doesn't have the type term()
The clause on line 1 cannot be reached
probably because it can't handle dialyzer module attributes
Hi @tim2CF
-
we were thinking about adding gradualizer specific attributes.
-
in the general case I imagine dialyzer options are not really applicable for gradualizer. however in the special case of
:nowarn_functionit makes sense, and could result in Gradualizer just skipping checking that function all together. -
if Gradualizer would parse and understand a few dialyzer attributes that would reduce a bit of noise and duplication in the code. Although in the general case it would be good not to mix the two and gradualizer to have its separate module-inlined options.
-
in this particular example any pattern should be a subtype of
term()so there must be some other error, I suspect a bug in Gradualizer. Did you run Gradualizer on theProtocolmodule itself or could you post a minimal Elixir code example that triggers the same error?
Hi again @gomoripeti Thanks for response! This bug appears if I'm trying to define any custom protocol, so minimal Elixir code is
defprotocol Foo do
def bar(_)
end
If we translate generated BEAM file to Erlang, probably problematic place is
-spec impl_for(term()) -> atom() | nil.
impl_for(#{'__struct__' := __@2 = __@1})
when erlang:is_atom(__@2) ->
struct_impl_for(__@1);
Full snipped of decompiled Erlang code is
here
-file("lib/foo.ex", 1).
-module('Elixir.Foo').
-compile([no_auto_import, debug_info,
{inline, [{struct_impl_for, 1}]}]).
-callback bar(t()) -> term().
-spec 'impl_for!'(term()) -> atom().
-spec impl_for(term()) -> atom() | nil.
-spec '__protocol__'(module) -> 'Elixir.Foo';
(functions) -> [{bar, 1}, ...];
('consolidated?') -> boolean();
(impls) -> not_consolidated |
{consolidated, [module()]}.
-export_type([t/0]).
-type t() :: term().
-dialyzer({nowarn_function,
[{'__protocol__', 1}, {impl_for, 1},
{'impl_for!', 1}]}).
-protocol([{fallback_to_any, false}]).
-export(['__info__'/1, '__protocol__'/1, bar/1,
impl_for/1, 'impl_for!'/1]).
-spec '__info__'(attributes | compile | functions |
macros | md5 | module | deprecated) -> any().
'__info__'(module) -> 'Elixir.Foo';
'__info__'(functions) ->
[{'__protocol__', 1}, {bar, 1}, {impl_for, 1},
{'impl_for!', 1}];
'__info__'(macros) -> [];
'__info__'(Key = attributes) ->
erlang:get_module_info('Elixir.Foo', Key);
'__info__'(Key = compile) ->
erlang:get_module_info('Elixir.Foo', Key);
'__info__'(Key = md5) ->
erlang:get_module_info('Elixir.Foo', Key);
'__info__'(deprecated) -> [].
'__protocol__'(module) -> 'Elixir.Foo';
'__protocol__'(functions) -> [{bar, 1}];
'__protocol__'('consolidated?') -> false;
'__protocol__'(impls) -> not_consolidated.
bar(__@1) -> ('impl_for!'(__@1)):bar(__@1).
impl_for(#{'__struct__' := __@2 = __@1})
when erlang:is_atom(__@2) ->
struct_impl_for(__@1);
impl_for(__@1) when erlang:is_tuple(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Tuple')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Tuple', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Tuple':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_atom(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Atom')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Atom', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Atom':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_list(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.List')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.List', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.List':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_map(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Map')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Map', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Map':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_bitstring(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.BitString')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.BitString',
'__impl__', 1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.BitString':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_integer(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Integer')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Integer',
'__impl__', 1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Integer':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_float(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Float')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Float', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Float':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_function(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Function')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Function',
'__impl__', 1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Function':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_pid(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.PID')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.PID', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.PID':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_port(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Port')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Port', '__impl__',
1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Port':'__impl__'(target);
false -> nil
end;
impl_for(__@1) when erlang:is_reference(__@1) ->
case case
'Elixir.Code':'ensure_compiled?'('Elixir.Foo.Reference')
of
false -> false;
true ->
erlang:function_exported('Elixir.Foo.Reference',
'__impl__', 1);
__@2 -> erlang:error({badbool, 'and', __@2})
end
of
true -> 'Elixir.Foo.Reference':'__impl__'(target);
false -> nil
end;
impl_for(_) -> nil.
'impl_for!'(__@1) ->
case impl_for(__@1) of
__@2 when __@2 =:= nil orelse __@2 =:= false ->
erlang:error('Elixir.Protocol.UndefinedError':exception([{protocol,
'Elixir.Foo'},
{value,
__@1}]));
__@3 -> __@3
end.
struct_impl_for(__@1) ->
__@2 = 'Elixir.Module':concat('Elixir.Foo', __@1),
case case 'Elixir.Code':'ensure_compiled?'(__@2) of
false -> false;
true -> erlang:function_exported(__@2, '__impl__', 1);
__@3 -> erlang:error({badbool, 'and', __@3})
end
of
true -> __@2:'__impl__'(target);
false -> nil
end.
thanks for the example, I have a look into it