-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Compute sizes of bit expressions and patterns
- Loading branch information
1 parent
d74675a
commit 5e1c425
Showing
8 changed files
with
234 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
%% Helper module for binaries/bitstrings | ||
-module(gradualizer_bin). | ||
|
||
-export([compute_type/1]). | ||
|
||
%% Computes the type of a bitstring expression or pattern based on the sizes | ||
%% of the elements. The returned type is a normalized bitstring type. | ||
-spec compute_type(ExprOrPat) -> erl_parse:abstract_type() | ||
when ExprOrPat :: {bin, _, _}, | ||
ExprOrPat :: erl_parse:abstract_expr(). | ||
compute_type(Bin) -> | ||
View = bin_view(Bin), | ||
bitstr_view_to_type(View). | ||
|
||
%% <<_:B, _:_*U>> is represented as {B, U} (fixed base + multiple of unit) | ||
-type bitstr_view() :: {non_neg_integer(), non_neg_integer()} | none. | ||
|
||
bitstr_concat({B1, U1}, {B2, U2}) -> | ||
{B1 + B2, gcd(U1, U2)}; | ||
bitstr_concat(none, _) -> none; | ||
bitstr_concat(_, none) -> none. | ||
|
||
-spec bitstr_view_to_type(bitstr_view()) -> erl_parse:abstract_type(). | ||
bitstr_view_to_type({B, U}) -> | ||
Anno = erl_anno:new(0), | ||
{type, Anno, binary, [{integer, Anno, B}, {integer, Anno, U}]}; | ||
bitstr_view_to_type(none) -> | ||
{type, erl_anno:new(0), none, []}. | ||
|
||
%% Returns the view of a bit expression or pattern, i.e. computes its size | ||
-spec bin_view({bin, _, _}) -> bitstr_view(). | ||
bin_view({bin, _, BinElements}) -> | ||
ElementViews = [bin_element_view(E) || E <- BinElements], | ||
lists:foldl(fun bitstr_concat/2, {0, 0}, ElementViews). | ||
|
||
bin_element_view({bin_element, Anno, {Lit, _, _}, default, _Spec} = BinElem) | ||
when Lit == integer; Lit == char; Lit == string -> | ||
%% Literal with default size, i.e. no variables to consider. | ||
%% Size it not allowed for utf8/utf16/utf32. | ||
Bin = {bin, Anno, [BinElem]}, | ||
{value, Value, []} = erl_eval:expr(Bin, []), | ||
{bit_size(Value), 0}; | ||
bin_element_view({bin_element, Anno, {string, _, Chars}, Size, Spec}) -> | ||
%% Expand <<"ab":32/float>> to <<$a:32/float, $b:32/float>> | ||
%% FIXME: Not true for float, integer | ||
Views = [bin_element_view({bin_element, Anno, {char, Anno, Char}, Size, Spec}) | ||
|| Char <- Chars], | ||
lists:foldl(fun bitstr_concat/2, {0, 0}, Views); | ||
bin_element_view({bin_element, _Anno, _Expr, default, Specifiers}) -> | ||
%% Default size | ||
%% <<1/integer-unit:2>> gives the following error: | ||
%% * 1: a bit unit size must not be specified unless a size is specified too | ||
%% However <<(<<9:9>>)/binary-unit:3>> gives no error. | ||
%% The type specifier 'binary' seems to be the only exception though. | ||
case get_type_specifier(Specifiers) of | ||
integer -> {8, 0}; | ||
float -> {64, 0}; | ||
binary -> {0, get_unit(Specifiers)}; | ||
bytes -> {0, 8}; | ||
bitstring -> {0, 1}; | ||
bits -> {0, 1}; | ||
utf8 -> {0, 8}; %% 1-4 bytes | ||
utf16 -> {0, 16}; %% 2-4 bytes | ||
utf32 -> {32, 0} %% 4 bytes, fixed | ||
end; | ||
bin_element_view({bin_element, _Anno, _Expr, SizeSpec, Specifiers}) -> | ||
%% Non-default size, possibly a constant expression | ||
try erl_eval:expr(SizeSpec, []) of | ||
{value, Sz, _VarBinds} -> | ||
{Sz * get_unit(Specifiers), 0} | ||
catch | ||
error:{unbound_var, _} -> | ||
%% Variable size | ||
U = get_unit(Specifiers), | ||
case get_type_specifier(Specifiers) of | ||
float when U == 64 -> {64, 0}; %% size must be 1 in this case | ||
float -> {32, 32}; %% a float must be 32 or 64 bits | ||
_OtherType -> {0, U} %% any multiple of the unit | ||
end | ||
end. | ||
|
||
-spec get_type_specifier(Specifiers :: [atom() | {unit, non_neg_integer()}] | | ||
default) -> atom(). | ||
get_type_specifier(Specifiers) when is_list(Specifiers) -> | ||
case [S || S <- Specifiers, | ||
S == integer orelse S == float orelse | ||
S == binary orelse S == bytes orelse | ||
S == bitstring orelse S == bits orelse | ||
S == utf8 orelse S == utf16 orelse | ||
S == utf32] of | ||
[S|_] -> S; | ||
[] -> integer %% default | ||
end; | ||
get_type_specifier(default) -> integer. | ||
|
||
get_unit(Specifiers) when is_list(Specifiers) -> | ||
case [U || {unit, U} <- Specifiers] of | ||
[U|_] -> U; | ||
[] -> get_default_unit(Specifiers) | ||
end; | ||
get_unit(default) -> 1. | ||
|
||
get_default_unit(Specifiers) when is_list(Specifiers) -> | ||
case get_type_specifier(Specifiers) of | ||
binary -> 8; | ||
bytes -> 8; | ||
_Other -> 1 | ||
end. | ||
|
||
-spec gcd(non_neg_integer(), non_neg_integer()) -> non_neg_integer(). | ||
gcd(A, B) when B > A -> gcd1(B, A); | ||
gcd(A, B) -> gcd1(A, B). | ||
|
||
-spec gcd1(non_neg_integer(), non_neg_integer()) -> non_neg_integer(). | ||
gcd1(A, 0) -> A; | ||
gcd1(A, B) -> | ||
case A rem B of | ||
0 -> B; | ||
X -> gcd1(B, X) | ||
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
-module(gradualizer_bin_tests). | ||
|
||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
%% Parse type and expression | ||
t(Str) -> typelib:remove_pos(typelib:parse_type(Str)). | ||
e(Str) -> merl:quote(Str). | ||
|
||
-define(_assert_bin_type(T, E), | ||
{??E, ?_assertEqual(t(??T), gradualizer_bin:compute_type(e(??E)))}). | ||
|
||
compute_type_combined_test_() -> | ||
[ | ||
?_assert_bin_type(<<_:14, _:_*3>>, <<0:14, N/binary-unit:3>>), | ||
?_assert_bin_type(<<_:14, _:_*3>>, <<0:N/integer-unit:3, "ab":7/integer>>) | ||
]. | ||
|
||
compute_type_utf_test_() -> | ||
[ | ||
?_assert_bin_type(<<_:24>>, <<"abc"/utf8>>), | ||
?_assert_bin_type(<<_:48>>, <<"abc"/utf16>>), | ||
?_assert_bin_type(<<_:96>>, <<"abc"/utf32>>), | ||
?_assert_bin_type(<<_:_*8>>, <<X/utf8>>), | ||
?_assert_bin_type(<<_:_*16>>, <<X/utf16>>), | ||
?_assert_bin_type(<<_:32>>, <<X/utf32>>) | ||
]. | ||
|
||
compute_type_test_() -> | ||
[ | ||
?_assert_bin_type(<<_:8>>, <<X>>), | ||
?_assert_bin_type(<<_:3>>, <<X:3>>), | ||
?_assert_bin_type(<<_:_*1>>, <<X:N>>), | ||
?_assert_bin_type(<<_:12>>, <<X:3/unit:4>>), | ||
?_assert_bin_type(<<_:_*4>>, <<X:N/unit:4>>) | ||
]. | ||
|
||
compute_type_bitstring_test_() -> | ||
[ | ||
?_assert_bin_type(<<_:8>>, <<X:1/binary>>), | ||
?_assert_bin_type(<<_:42>>, <<X:7/binary-unit:6>>), | ||
?_assert_bin_type(<<_:_*8>>, <<X/binary>>), | ||
?_assert_bin_type(<<_:_*2>>, <<X/binary-unit:2>>), | ||
?_assert_bin_type(<<_:_*8>>, <<X/bytes>>), | ||
?_assert_bin_type(<<_:16>>, <<X:2/bytes>>), | ||
?_assert_bin_type(<<_:_*1>>, <<X/bitstring>>), | ||
?_assert_bin_type(<<_:_*1>>, <<X/bits>>) | ||
]. | ||
|
||
compute_type_float_test_() -> | ||
[ | ||
?_assert_bin_type(<<_:64>>, <<X/float>>), | ||
?_assert_bin_type(<<_:32>>, <<X:32/float>>), | ||
?_assert_bin_type(<<_:32, _:_*32>>, <<X:S/float>>), | ||
?_assert_bin_type(<<_:32, _:_*32>>, <<X:S/float-unit:16>>), | ||
?_assert_bin_type(<<_:64>>, <<X:S/float-unit:64>>) | ||
]. | ||
|
||
-ifdef(OTP_RELEASE). | ||
%% Run only in OTP 21 | ||
compute_type_float_string_test_() -> | ||
[ | ||
?_assert_bin_type(<<_:192>>, <<"abc"/float>>), | ||
?_assert_bin_type(<<_:96>>, <<"abc":32/float>>), | ||
?_assert_bin_type(<<_:96, _:_*32>>, <<"abc":S/float>>) | ||
]. | ||
-else. | ||
compute_type_float_string_test_() -> | ||
{"Skipping <<\"str\"/float>> tests in this OTP release", []}. | ||
-endif. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-module(bin_expression_1). | ||
|
||
-spec bin_1() -> binary(). | ||
bin_1() -> | ||
<<1:1>>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-module(bin_expression_2). | ||
|
||
-spec bin_2(any(), any()) -> <<_:_*6>>. | ||
bin_2(A, B) -> | ||
<<0:A/integer-unit:27, 1:B/integer-unit:30>>. |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters