From d3fb5985064b8fda0f45ab0a6994e62e7baee56f Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Tue, 23 Sep 2025 15:42:53 +1000 Subject: [PATCH] coerce bits The thing to remember about bits is that they are actually integers... It is tempting to present bits as binaries, but that hides the nuance of the infinite leading zeroes, the potential for infinite leading ones, etc. --- src/hz.erl | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 9c3b202..4eb7817 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1848,8 +1848,16 @@ coerce({O, N, string}, Str, Direction) -> StrBin -> {ok, StrBin} end; -coerce({O, N, {bytes, [Count]}}, Bytes, _Direction) when is_binary(Bytes) -> +coerce({O, N, {bytes, [Count]}}, Bytes, _Direction) when is_bitstring(Bytes) -> coerce_bytes(O, N, Count, Bytes); +coerce({_, _, bits}, {bits, Num}, from_fate) -> + {ok, Num}; +coerce({_, _, bits}, Num, to_fate) when is_integer(Num) -> + {ok, {bits, Num}}; +coerce({_, _, bits}, Bits, to_fate) when is_bitstring(Bits) -> + Size = bit_size(Bits), + <> = Bits, + {ok, {bits, IntValue}}; coerce({_, _, {list, [Type]}}, Data, Direction) when is_list(Data) -> coerce_list(Type, Data, Direction); coerce({_, _, {map, [KeyType, ValType]}}, Data, Direction) when is_map(Data) -> @@ -2524,6 +2532,14 @@ coerce_record_test() -> {ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}), try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). +coerce_bytes_test() -> + {ok, Type} = annotate_type({tuple, [{bytes, [4]}, {bytes, [any]}]}, #{}), + try_coerce(Type, {<<"abcd">>, <<"efghi">>}, {tuple, {<<"abcd">>, <<"efghi">>}}). + +bits_test() -> + {ok, Type} = annotate_type(bits, #{}), + try_coerce(Type, 5, {bits, 5}). + %%% Complex AACI paramter and namespace tests @@ -2610,12 +2626,27 @@ param_test() -> try_coerce(Input, 0, 0), try_coerce(Output, 0, 0). -bytes_test() -> +%%% Obscure Sophia types where we should check the AACI as well + +obscure_aaci_test() -> Contract = " contract C = - entrypoint f(): bytes(4) * bytes() = (#DEADBEEF, Bytes.to_any_size(#112233)) + entrypoint options(): option(int) = None + entrypoint fixed_bytes(): bytes(4) = #DEADBEEF + entrypoint any_bytes(): bytes() = Bytes.to_any_size(#112233) + entrypoint bits(): bits = Bits.all ", {ok, AACI} = aaci_from_string(Contract), - {ok, {[], Output}} = aaci_lookup_spec(AACI, "f"), - try_coerce(Output, {<<"abcd">>, <<"efghi">>}, {tuple, {<<"abcd">>, <<"efghi">>}}). + + IntAnnotated = {integer, already_normalized, integer}, + OptionFlat = {variant, [{"None", []}, {"Some", [IntAnnotated]}]}, + OptionAnnotated = {{option, [integer]}, already_normalized, OptionFlat}, + {ok, {[], OptionAnnotated}} = aaci_lookup_spec(AACI, "options"), + + {ok, {[], {_, _, {bytes, [4]}}}} = aaci_lookup_spec(AACI, "fixed_bytes"), + {ok, {[], {_, _, {bytes, [any]}}}} = aaci_lookup_spec(AACI, "any_bytes"), + + {ok, {[], {_, _, bits}}} = aaci_lookup_spec(AACI, "bits"), + + ok.