Skip to content

Commit

Permalink
Squelch dialyzer warns for translated type encoders
Browse files Browse the repository at this point in the history
When a (scalar) type always operate on data from a translation
function, then emit a dialyzer nowarn_function attribyte (if
supported for the target erlang version).

The reasoning is that the type encoders are written for the general
case for inputs over the entire supported range, but when all fields
get their data via a translation, it can be that dialyzer can see
that a general-case function clause is dead code with this a
particular translator.
  • Loading branch information
tomas-abrahamsson committed Dec 5, 2021
1 parent 11145c4 commit 51f5656
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 31 deletions.
82 changes: 51 additions & 31 deletions src/gpb_gen_encoders.erl
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ format_encoders_top_function_msgs(Defs, AnRes, Opts) ->
|| {{msg,MsgName}, _Fields}=MsgDef <- Defs]]
|| gpb_lib:get_epb_functions_by_opts(Opts)]].

format_aux_encoders(Defs, AnRes, _Opts) ->
format_aux_encoders(Defs, AnRes, Opts) ->
[format_enum_encoders(Defs, AnRes),
format_type_encoders()
format_type_encoders(AnRes, Opts)
].

format_aux_common_encoders(_Defs, AnRes, _Opts) ->
Expand Down Expand Up @@ -919,27 +919,27 @@ format_unknownsize_packed_field_encoder2(MsgName,
[replace_term('<encode-elem>', ElemEncoderFn),
replace_term('Tr', Transl)])].

format_type_encoders() ->
[format_varlength_field_encoders(),
format_fixlength_field_encoders(),
format_type_encoders(AnRes, Opts) ->
[format_varlength_field_encoders(AnRes, Opts),
format_fixlength_field_encoders(AnRes, Opts),
format_unknown_encoder(),
format_varint_encoder()].

format_varlength_field_encoders() ->
format_varlength_field_encoders(AnRes, Opts) ->
[format_sint_encoder(),
format_int_encoder(int32, 32),
format_int_encoder(int64, 64),
format_bool_encoder(),
format_string_encoder(),
format_bytes_encoder()].
format_int_encoder(int32, 32, AnRes, Opts),
format_int_encoder(int64, 64, AnRes, Opts),
format_bool_encoder(AnRes, Opts),
format_string_encoder(AnRes, Opts),
format_bytes_encoder(AnRes, Opts)].

format_fixlength_field_encoders() ->
[format_fixed_encoder(fixed32, 32, [little]),
format_fixed_encoder(sfixed32, 32, [little,signed]),
format_fixed_encoder(fixed64, 64, [little]),
format_fixed_encoder(sfixed64, 64, [little,signed]),
format_float_encoder(float),
format_double_encoder(double)].
format_fixlength_field_encoders(AnRes, Opts) ->
[format_fixed_encoder(fixed32, 32, [little], AnRes, Opts),
format_fixed_encoder(sfixed32, 32, [little,signed], AnRes, Opts),
format_fixed_encoder(fixed64, 64, [little], AnRes, Opts),
format_fixed_encoder(sfixed64, 64, [little,signed], AnRes, Opts),
format_float_encoder(float, AnRes, Opts),
format_double_encoder(double, AnRes, Opts)].

format_sint_encoder() ->
[gpb_lib:nowarn_unused_function(e_type_sint,3),
Expand All @@ -951,9 +951,10 @@ format_sint_encoder() ->
e_varint(Value * -2 - 1, Bin)
end)].

format_int_encoder(Type, _BitLen) ->
format_int_encoder(Type, _BitLen, AnRes, Opts) ->
FnName = gpb_lib:mk_fn(e_type_, Type),
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(Type, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
FnName,
fun(Value, Bin, _TrUserData) when 0 =< Value, Value =< 127 ->
Expand All @@ -966,19 +967,22 @@ format_int_encoder(Type, _BitLen) ->
e_varint(N, Bin)
end)].

format_bool_encoder() ->
[gpb_lib:nowarn_unused_function(e_type_bool, 3),
format_bool_encoder(AnRes, Opts) ->
FnName = e_type_bool,
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(bool, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
e_type_bool,
FnName,
fun(true, Bin, _TrUserData) -> <<Bin/binary, 1>>;
(false, Bin, _TrUserData) -> <<Bin/binary, 0>>;
(1, Bin, _TrUserData) -> <<Bin/binary, 1>>;
(0, Bin, _TrUserData) -> <<Bin/binary, 0>>
end)].

format_fixed_encoder(Type, BitLen, BitType) ->
format_fixed_encoder(Type, BitLen, BitType, AnRes, Opts) ->
FnName = gpb_lib:mk_fn(e_type_, Type),
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(Type, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
FnName,
fun(Value, Bin, _TrUserData) ->
Expand Down Expand Up @@ -1027,9 +1031,10 @@ format_packed_double_encoder(FnName, TranslFn) ->
end,
[replace_term('Tr', TranslFn)]).

format_float_encoder(Type) ->
format_float_encoder(Type, AnRes, Opts) ->
FnName = gpb_lib:mk_fn(e_type_, Type),
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(Type, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
FnName,
fun(V, Bin, _) when is_number(V) -> <<Bin/binary, V:32/little-float>>;
Expand All @@ -1038,9 +1043,10 @@ format_float_encoder(Type) ->
(nan, Bin, _) -> <<Bin/binary, 0:16,192,127>>
end)].

format_double_encoder(Type) ->
format_double_encoder(Type, AnRes, Opts) ->
FnName = gpb_lib:mk_fn(e_type_, Type),
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(Type, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
FnName,
fun(V, Bin, _) when is_number(V) -> <<Bin/binary, V:64/little-float>>;
Expand All @@ -1049,20 +1055,24 @@ format_double_encoder(Type) ->
(nan, Bin, _) -> <<Bin/binary, 0:48,248,127>>
end)].

format_string_encoder() ->
[gpb_lib:nowarn_unused_function(e_type_string, 3),
format_string_encoder(AnRes, Opts) ->
FnName = e_type_string,
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(string, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
e_type_string,
FnName,
fun(S, Bin, _TrUserData) ->
Utf8 = unicode:characters_to_binary(S),
Bin2 = e_varint(byte_size(Utf8), Bin),
<<Bin2/binary, Utf8/binary>>
end)].

format_bytes_encoder() ->
[gpb_lib:nowarn_unused_function(e_type_bytes, 3),
format_bytes_encoder(AnRes, Opts) ->
FnName = e_type_bytes,
[gpb_lib:nowarn_unused_function(FnName, 3),
maybe_no_dialyzer_warn_funcion(bytes, FnName, 3, AnRes, Opts),
gpb_codegen:format_fn(
e_type_bytes,
FnName,
fun(Bytes, Bin, _TrUserData) when is_binary(Bytes) ->
Bin2 = e_varint(byte_size(Bytes), Bin),
<<Bin2/binary, Bytes/binary>>;
Expand Down Expand Up @@ -1126,6 +1136,16 @@ format_is_empty_string(#anres{has_p3_opt_strings=true}) ->
([]) -> false
end)].

maybe_no_dialyzer_warn_funcion(Type, FnName, Arity,
#anres{types_only_via_translations=TrTypes},
Opts) ->
case sets:is_element(Type, TrTypes) of
true ->
gpb_lib:nowarn_dialyzer_attr(FnName, Arity, Opts);
false ->
[]
end.

ret_type_all_msgs(Defs) ->
case at_least_one_msg_is_nonempty(Defs) of
true -> "binary()";
Expand Down
47 changes: 47 additions & 0 deletions test/gpb_compile_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
%% Translators for top-level message tests
-export([e_value_to_msg/2, d_msg_to_value/2, v_value/2]).

%% Translators for bytes <--> octet-lists
-export([e_l2b/1, d_b2l/1, v_l/1]).


-ifdef(OTP_RELEASE).
-define(STACKTRACE(C,R,St), C:R:St ->).
Expand Down Expand Up @@ -1232,6 +1235,15 @@ assert_contains_regexp(IoData, Re) ->
erlang:error({"Re ", Re, "not found in", IoData})
end.

assert_not_contains_regexp(IoData, Re) ->
case re:run(IoData, Re) of
{match, _} ->
?debugFmt("~nERROR: Regexp ~s found in:~n~s~n", [Re, IoData]),
erlang:error({"Re ", Re, "not found in", IoData});
nomatch ->
ok
end.

%% --- bytes ----------

list_as_bytes_indata_test() ->
Expand Down Expand Up @@ -1765,6 +1777,35 @@ verify_callback_with_and_without_errorf_test() ->
Mod2:verify_msg(#m1{a=123})),
unload_code(Mod2).

dialzer_nowarn_when_scalar_only_for_translated_fields_test_() ->
case gpb_lib:nowarn_dialyzer_attr(a, 0, []) of
"" -> %
{"dialzer_nowarn_when_scalar_only_for_translated_fields_test_"
" skipped on older Erlang", []};
_ ->
{"dialzer_nowarn_when_scalar_only_for_translated_fields",
[fun dialzer_nowarn_when_scalar_only_for_translated_fields_aux/0]}
end.

dialzer_nowarn_when_scalar_only_for_translated_fields_aux() ->
Proto1 = ["message M {\n"
" required bytes f1=1;\n" % translated
"};\n"],
Proto2 = ["message M {\n"
" required bytes f1=1;\n" % translated
" required bytes f2=2;\n" % not translated
"};\n"],
TranslOpt = {translate_field,
{['M', f1], [{encode,{?MODULE,e_l2b,['$1']}},
{decode,{?MODULE,d_b2l,['$1']}},
{verify,{?MODULE,v_l,['$1']}}]}},
S1 = compile_to_string(Proto1, [TranslOpt]),
S2 = compile_to_string(Proto2, [TranslOpt]),
assert_contains_regexp(S1, "-dialyzer.*nowarn_function.*e_type_bytes"),
assert_not_contains_regexp(S2, "-dialyzer.*nowarn_function.*e_type_bytes"),
ok.


%% Translators/callbacks:
any_e_atom(A) ->
{'google.protobuf.Any', "x.com/atom", list_to_binary(atom_to_list(A))}.
Expand Down Expand Up @@ -2212,6 +2253,12 @@ v_value(Value, integer) when is_integer(Value) -> ok;
v_value(Value, string) when is_list(Value) -> ok;
v_value(X, Expected) -> error({bad_value, Expected, X}).

e_l2b(L) -> list_to_binary(L).

d_b2l(B) -> binary_to_list(B).

v_l(L) when is_list(L) -> ok.

verify_is_optional_for_translate_toplevel_messages_test() ->
M = compile_iolist(
["message m1 {",
Expand Down

0 comments on commit 51f5656

Please sign in to comment.