unwrap fate_to_erlang results
fate_to_erlang can only really fail at runtime if the wrong AACI is provided, in which case the details of how failure occured are not helpful, or recoverable. Anything else will be so broken that dialyzer will catch it, or is a bug in hakuzaru, that we want to know about.
This commit is contained in:
+120
-171
@@ -527,10 +527,7 @@ opaque_type(Params, #{record := FieldDefs}) ->
|
||||
|| #{name := Name, type := Type} <- FieldDefs],
|
||||
{record, Fields};
|
||||
opaque_type(Params, #{variant := VariantDefs}) ->
|
||||
ConvertVariant = fun(Pair) ->
|
||||
[{Name, Types}] = maps:to_list(Pair),
|
||||
{binary_to_list(Name), [opaque_type(Params, Type) || Type <- Types]}
|
||||
end,
|
||||
ConvertVariant = fun(Pair) -> opaque_variant_each(Params, Pair) end,
|
||||
Variants = lists:map(ConvertVariant, VariantDefs),
|
||||
{variant, Variants};
|
||||
opaque_type(Params, #{tuple := TypeDefs}) ->
|
||||
@@ -541,6 +538,11 @@ opaque_type(Params, Pair) when is_map(Pair) ->
|
||||
[{Name, TypeArgs}] = maps:to_list(Pair),
|
||||
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
||||
|
||||
opaque_variant_each(Params, Pair) ->
|
||||
[{Name, Types}] = maps:to_list(Pair),
|
||||
ElemTypes = [opaque_type(Params, Type) || Type <- Types],
|
||||
{binary_to_list(Name), ElemTypes}.
|
||||
|
||||
|
||||
-spec opaque_type_name(binary()) -> atom() | string().
|
||||
% Atoms for any builtins that aren't qualified by a namespace in Sophia.
|
||||
@@ -848,7 +850,7 @@ erlang_args_to_fate(VarTypes, Terms) ->
|
||||
DefLength = length(VarTypes),
|
||||
ArgLength = length(Terms),
|
||||
if
|
||||
DefLength =:= ArgLength -> coerce_zipped_bindings(lists:zip(VarTypes, Terms), to_fate, arg);
|
||||
DefLength =:= ArgLength -> coerce_zipped_bindings(lists:zip(VarTypes, Terms), arg);
|
||||
DefLength > ArgLength -> {error, too_few_args};
|
||||
DefLength < ArgLength -> {error, too_many_args}
|
||||
end.
|
||||
@@ -937,19 +939,19 @@ erlang_to_fate({_, _, bits}, Bits) when is_bitstring(Bits) ->
|
||||
<<IntValue:Size>> = Bits,
|
||||
{ok, {bits, IntValue}};
|
||||
erlang_to_fate({_, _, {list, [Type]}}, Data) when is_list(Data) ->
|
||||
coerce_list(Type, Data, to_fate);
|
||||
coerce_list(Type, Data);
|
||||
erlang_to_fate({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) ->
|
||||
coerce_map(KeyType, ValType, Data, to_fate);
|
||||
coerce_map(KeyType, ValType, Data);
|
||||
erlang_to_fate({O, N, {tuple, ElementTypes}}, Data) when is_tuple(Data) ->
|
||||
ElementList = tuple_to_list(Data),
|
||||
coerce_tuple(O, N, ElementTypes, ElementList, to_fate);
|
||||
coerce_tuple(O, N, ElementTypes, ElementList);
|
||||
erlang_to_fate({O, N, {variant, Variants}}, Name) when is_list(Name) ->
|
||||
erlang_to_fate({O, N, {variant, Variants}}, {Name});
|
||||
erlang_to_fate({O, N, {variant, Variants}}, Data) when is_tuple(Data), tuple_size(Data) > 0 ->
|
||||
[Name | Terms] = tuple_to_list(Data),
|
||||
case lookup_variant(Name, Variants) of
|
||||
{Tag, TermTypes} ->
|
||||
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, to_fate);
|
||||
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms);
|
||||
not_found ->
|
||||
ValidNames = [Valid || {Valid, _} <- Variants],
|
||||
single_error({invalid_variant, O, N, Name, ValidNames})
|
||||
@@ -957,17 +959,15 @@ erlang_to_fate({O, N, {variant, Variants}}, Data) when is_tuple(Data), tuple_siz
|
||||
erlang_to_fate({O, N, {record, MemberTypes}}, Map) when is_map(Map) ->
|
||||
coerce_map_to_record(O, N, MemberTypes, Map);
|
||||
erlang_to_fate({O, N, {unknown_type, _}}, Data) ->
|
||||
case N of
|
||||
already_normalized ->
|
||||
Message = "Warning: Unknown type ~p. Using term ~p as is.~n",
|
||||
io:format(Message, [O, Data]);
|
||||
_ ->
|
||||
Message = "Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n",
|
||||
io:format(Message, [O, N, Data])
|
||||
end,
|
||||
warn_unknown_type(O, N, Data),
|
||||
{ok, Data};
|
||||
erlang_to_fate({O, N, _}, Data) -> single_error({invalid, O, N, Data}).
|
||||
|
||||
warn_unknown_type(O, already_normalized, Data) ->
|
||||
io:format("Warning: Unknown type ~p. Using term ~p as is.~n", [O, Data]);
|
||||
warn_unknown_type(O, N, Data) ->
|
||||
io:format("Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n", [O, N, Data]).
|
||||
|
||||
coerce_chain_object(_, _, _, _, {raw, Binary}) ->
|
||||
{ok, Binary};
|
||||
coerce_chain_object(O, N, T, Tag, S) ->
|
||||
@@ -1000,69 +1000,69 @@ check_bytes(O, N, Count, Bytes) when byte_size(Bytes) /= Count ->
|
||||
check_bytes(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
coerce_zipped_bindings(Bindings, Direction, Tag) ->
|
||||
coerce_zipped_bindings(Bindings, Direction, Tag, [], []).
|
||||
coerce_zipped_bindings(Bindings, Tag) ->
|
||||
coerce_zipped_bindings(Bindings, Tag, [], []).
|
||||
|
||||
coerce_zipped_bindings([Next | Rest], Direction, Tag, Good, Broken) ->
|
||||
coerce_zipped_bindings([Next | Rest], Tag, Good, Broken) ->
|
||||
{{ArgName, Type}, Term} = Next,
|
||||
case coerce_direction(Type, Term, Direction) of
|
||||
case erlang_to_fate(Type, Term) of
|
||||
{ok, NewTerm} ->
|
||||
coerce_zipped_bindings(Rest, Direction, Tag, [NewTerm | Good], Broken);
|
||||
coerce_zipped_bindings(Rest, Tag, [NewTerm | Good], Broken);
|
||||
{error, Errors} ->
|
||||
Wrapped = wrap_errors({Tag, ArgName}, Errors),
|
||||
coerce_zipped_bindings(Rest, Direction, Tag, Good, [Wrapped | Broken])
|
||||
coerce_zipped_bindings(Rest, Tag, Good, [Wrapped | Broken])
|
||||
end;
|
||||
coerce_zipped_bindings([], _, _, Good, []) ->
|
||||
coerce_zipped_bindings([], _, Good, []) ->
|
||||
{ok, lists:reverse(Good)};
|
||||
coerce_zipped_bindings([], _, _, _, Broken) ->
|
||||
coerce_zipped_bindings([], _, _, Broken) ->
|
||||
{error, combine_errors(Broken)}.
|
||||
|
||||
coerce_list(Type, Elements, Direction) ->
|
||||
coerce_list(Type, Elements) ->
|
||||
% 0 index since it represents a sophia list
|
||||
coerce_list(Type, Elements, Direction, 0, [], []).
|
||||
coerce_list(Type, Elements, 0, [], []).
|
||||
|
||||
coerce_list(Type, [Next | Rest], Direction, Index, Good, Broken) ->
|
||||
case coerce_direction(Type, Next, Direction) of
|
||||
{ok, Coerced} -> coerce_list(Type, Rest, Direction, Index + 1, [Coerced | Good], Broken);
|
||||
coerce_list(Type, [Next | Rest], Index, Good, Broken) ->
|
||||
case erlang_to_fate(Type, Next) of
|
||||
{ok, Coerced} -> coerce_list(Type, Rest, Index + 1, [Coerced | Good], Broken);
|
||||
{error, Errors} ->
|
||||
Wrapped = wrap_errors({index, Index}, Errors),
|
||||
coerce_list(Type, Rest, Direction, Index + 1, Good, [Wrapped | Broken])
|
||||
coerce_list(Type, Rest, Index + 1, Good, [Wrapped | Broken])
|
||||
end;
|
||||
coerce_list(_Type, [], _, _, Good, []) ->
|
||||
coerce_list(_Type, [], _, Good, []) ->
|
||||
{ok, lists:reverse(Good)};
|
||||
coerce_list(_, [], _, _, _, Broken) ->
|
||||
coerce_list(_, [], _, _, Broken) ->
|
||||
{error, combine_errors(Broken)}.
|
||||
|
||||
coerce_map(KeyType, ValType, Data, Direction) ->
|
||||
coerce_map(KeyType, ValType, maps:iterator(Data), Direction, #{}, []).
|
||||
coerce_map(KeyType, ValType, Data) ->
|
||||
coerce_map(KeyType, ValType, maps:iterator(Data), #{}, []).
|
||||
|
||||
coerce_map(KeyType, ValType, Remaining, Direction, Good, Broken) ->
|
||||
coerce_map(KeyType, ValType, Remaining, Good, Broken) ->
|
||||
case maps:next(Remaining) of
|
||||
{K, V, RemainingAfter} ->
|
||||
coerce_map2(KeyType, ValType, RemainingAfter, Direction, Good, Broken, K, V);
|
||||
coerce_map2(KeyType, ValType, RemainingAfter, Good, Broken, K, V);
|
||||
none ->
|
||||
coerce_map_finish(Good, Broken)
|
||||
end.
|
||||
|
||||
coerce_map2(KeyType, ValType, Remaining, Direction, Good, Broken, K, V) ->
|
||||
case coerce_direction(KeyType, K, Direction) of
|
||||
coerce_map2(KeyType, ValType, Remaining, Good, Broken, K, V) ->
|
||||
case erlang_to_fate(KeyType, K) of
|
||||
{ok, KFATE} ->
|
||||
coerce_map3(KeyType, ValType, Remaining, Direction, Good, Broken, K, V, KFATE);
|
||||
coerce_map3(KeyType, ValType, Remaining, Good, Broken, K, V, KFATE);
|
||||
{error, Errors} ->
|
||||
Wrapped = wrap_errors(map_key, Errors),
|
||||
% Continue as if the key coerced successfully, so that we can give
|
||||
% errors for both the key and the value.
|
||||
coerce_map3(KeyType, ValType, Remaining, Direction, Good, [Wrapped | Broken], K, V, error)
|
||||
coerce_map3(KeyType, ValType, Remaining, Good, [Wrapped | Broken], K, V, error)
|
||||
end.
|
||||
|
||||
coerce_map3(KeyType, ValType, Remaining, Direction, Good, Broken, K, V, KFATE) ->
|
||||
case coerce_direction(ValType, V, Direction) of
|
||||
coerce_map3(KeyType, ValType, Remaining, Good, Broken, K, V, KFATE) ->
|
||||
case erlang_to_fate(ValType, V) of
|
||||
{ok, VFATE} ->
|
||||
NewGood = Good#{KFATE => VFATE},
|
||||
coerce_map(KeyType, ValType, Remaining, Direction, NewGood, Broken);
|
||||
coerce_map(KeyType, ValType, Remaining, NewGood, Broken);
|
||||
{error, Errors} ->
|
||||
Wrapped = wrap_errors({map_value, K}, Errors),
|
||||
coerce_map(KeyType, ValType, Remaining, Direction, Good, [Wrapped | Broken])
|
||||
coerce_map(KeyType, ValType, Remaining, Good, [Wrapped | Broken])
|
||||
end.
|
||||
|
||||
coerce_map_finish(Good, []) ->
|
||||
@@ -1079,13 +1079,10 @@ lookup_variant(Name, [_ | Rest], Tag) ->
|
||||
lookup_variant(_Name, [], _Tag) ->
|
||||
not_found.
|
||||
|
||||
coerce_tuple(O, N, TermTypes, Terms, Direction) ->
|
||||
case coerce_tuple_elements(TermTypes, Terms, Direction, tuple_element) of
|
||||
coerce_tuple(O, N, TermTypes, Terms) ->
|
||||
case coerce_elems_to_fate(TermTypes, Terms, tuple_element) of
|
||||
{ok, Converted} ->
|
||||
case Direction of
|
||||
to_fate -> {ok, {tuple, list_to_tuple(Converted)}};
|
||||
from_fate -> {ok, list_to_tuple(Converted)}
|
||||
end;
|
||||
{ok, {tuple, list_to_tuple(Converted)}};
|
||||
{error, too_few_terms} ->
|
||||
single_error({tuple_too_few_terms, O, N, list_to_tuple(Terms)});
|
||||
{error, too_many_terms} ->
|
||||
@@ -1093,19 +1090,14 @@ coerce_tuple(O, N, TermTypes, Terms, Direction) ->
|
||||
Errors -> Errors
|
||||
end.
|
||||
|
||||
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, Direction) ->
|
||||
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms) ->
|
||||
% FIXME: we could go through and add the variant tag to the adt_element
|
||||
% paths?
|
||||
case coerce_tuple_elements(TermTypes, Terms, Direction, adt_element) of
|
||||
case coerce_elems_to_fate(TermTypes, Terms, adt_element) of
|
||||
{ok, Converted} ->
|
||||
case Direction of
|
||||
to_fate ->
|
||||
Arities = [length(VariantTerms)
|
||||
|| {_, VariantTerms} <- Variants],
|
||||
{ok, {variant, Arities, Tag, list_to_tuple(Converted)}};
|
||||
from_fate ->
|
||||
{ok, list_to_tuple([Name | Converted])}
|
||||
end;
|
||||
Arities = [length(VariantTerms)
|
||||
|| {_, VariantTerms} <- Variants],
|
||||
{ok, {variant, Arities, Tag, list_to_tuple(Converted)}};
|
||||
{error, too_few_terms} ->
|
||||
single_error({adt_too_few_terms, O, N, Name, TermTypes, Terms});
|
||||
{error, too_many_terms} ->
|
||||
@@ -1113,32 +1105,32 @@ coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, Direction) ->
|
||||
Errors -> Errors
|
||||
end.
|
||||
|
||||
coerce_tuple_elements(Types, Terms, Direction, Tag) ->
|
||||
coerce_elems_to_fate(Types, Terms, Tag) ->
|
||||
% The sophia standard library uses 0 indexing for lists, and fst/snd/thd
|
||||
% for tuples... Not sure how we should report errors in tuples, then.
|
||||
coerce_tuple_elements(Types, Terms, Direction, Tag, 0, [], []).
|
||||
coerce_elems_to_fate(Types, Terms, Tag, 0, [], []).
|
||||
|
||||
coerce_tuple_elements([Type | Types], [Term | Terms], Direction, Tag, Index, Good, Broken) ->
|
||||
case coerce_direction(Type, Term, Direction) of
|
||||
coerce_elems_to_fate([Type | Types], [Term | Terms], Tag, Index, Good, Broken) ->
|
||||
case erlang_to_fate(Type, Term) of
|
||||
{ok, Value} ->
|
||||
coerce_tuple_elements(Types, Terms, Direction, Tag, Index + 1, [Value | Good], Broken);
|
||||
coerce_elems_to_fate(Types, Terms, Tag, Index + 1, [Value | Good], Broken);
|
||||
{error, Errors} ->
|
||||
Wrapped = wrap_errors({Tag, Index}, Errors),
|
||||
coerce_tuple_elements(Types, Terms, Direction, Tag, Index + 1, Good, [Wrapped | Broken])
|
||||
coerce_elems_to_fate(Types, Terms, Tag, Index + 1, Good, [Wrapped | Broken])
|
||||
end;
|
||||
coerce_tuple_elements([], [], _, _, _, Good, []) ->
|
||||
coerce_elems_to_fate([], [], _, _, Good, []) ->
|
||||
{ok, lists:reverse(Good)};
|
||||
coerce_tuple_elements([], [], _, _, _, _, Broken) ->
|
||||
coerce_elems_to_fate([], [], _, _, _, Broken) ->
|
||||
{error, combine_errors(Broken)};
|
||||
coerce_tuple_elements(_, [], _, _, _, _, _) ->
|
||||
coerce_elems_to_fate(_, [], _, _, _, _) ->
|
||||
{error, too_few_terms};
|
||||
coerce_tuple_elements([], _, _, _, _, _, _) ->
|
||||
coerce_elems_to_fate([], _, _, _, _, _) ->
|
||||
{error, too_many_terms}.
|
||||
|
||||
coerce_map_to_record(O, N, MemberTypes, Map) ->
|
||||
case zip_record_fields(MemberTypes, Map) of
|
||||
{ok, Zipped} ->
|
||||
case coerce_zipped_bindings(Zipped, to_fate, field) of
|
||||
case coerce_zipped_bindings(Zipped, field) of
|
||||
{ok, [SingleElem]} ->
|
||||
% Singleton records aren't implemented as FATE tuples at
|
||||
% all.
|
||||
@@ -1155,31 +1147,6 @@ coerce_map_to_record(O, N, MemberTypes, Map) ->
|
||||
single_error({unexpected_fields, O, N, Names})
|
||||
end.
|
||||
|
||||
coerce_record_to_map(O, N, MemberTypes, Tuple) ->
|
||||
{Names, Types} = lists:unzip(MemberTypes),
|
||||
Terms = tuple_to_list(Tuple),
|
||||
% FIXME: We could go through and change the record_element paths into field
|
||||
% paths?
|
||||
case coerce_tuple_elements(Types, Terms, from_fate, record_element) of
|
||||
{ok, Converted} ->
|
||||
Map = maps:from_list(lists:zip(Names, Converted)),
|
||||
{ok, Map};
|
||||
{error, too_few_terms} ->
|
||||
single_error({record_too_few_terms, O, N, Tuple});
|
||||
{error, too_many_terms} ->
|
||||
single_error({record_too_many_terms, O, N, Tuple});
|
||||
{error, Errors} ->
|
||||
correct_record_error_paths(Names, Errors)
|
||||
end.
|
||||
|
||||
correct_record_error_paths(Names, Errors) ->
|
||||
CorrectOne = fun({Error, [{record_element, N} | Path]}) ->
|
||||
FieldName = lists:nth(N + 1, Names),
|
||||
{Error, [{record_element, N, FieldName} | Path]}
|
||||
end,
|
||||
Corrected = lists:map(CorrectOne, Errors),
|
||||
{error, Corrected}.
|
||||
|
||||
zip_record_fields(Fields, Map) ->
|
||||
case lists:mapfoldl(fun zip_record_field/2, {Map, []}, Fields) of
|
||||
{_, {_, Missing = [_|_]}} ->
|
||||
@@ -1220,20 +1187,10 @@ combine_errors(Broken) ->
|
||||
|
||||
%%% FATE to Erlang
|
||||
|
||||
% Not sure if this is needed... fate_to_erlang shouldn't fail.
|
||||
coerce_direction(Type, Term, to_fate) ->
|
||||
erlang_to_fate(Type, Term);
|
||||
coerce_direction(Type, Term, from_fate) ->
|
||||
fate_to_erlang(Type, Term).
|
||||
|
||||
|
||||
-spec fate_to_erlang(Type, FATE) -> {ok, Erlang} | {error, Errors}
|
||||
-spec fate_to_erlang(Type, FATE) -> Erlang
|
||||
when Type :: annotated_type(),
|
||||
FATE :: gmb_fate_data:fate_type(),
|
||||
Erlang :: erlang_repr(),
|
||||
Errors :: [{Reason, [PathStep]}],
|
||||
Reason :: term(),
|
||||
PathStep :: term().
|
||||
Erlang :: erlang_repr().
|
||||
%% @doc
|
||||
%% Convert a FATE-flavored Erlang term into a Sophia-flavored Erlang term
|
||||
%% Typically this is called by hakuzaru for you when decoding results from the
|
||||
@@ -1243,86 +1200,81 @@ coerce_direction(Type, Term, from_fate) ->
|
||||
%% information.
|
||||
|
||||
fate_to_erlang({_, _, integer}, S) when is_integer(S) ->
|
||||
{ok, S};
|
||||
S;
|
||||
fate_to_erlang({_, _, address}, {address, Bin}) ->
|
||||
Address = gmser_api_encoder:encode(account_pubkey, Bin),
|
||||
{ok, unicode:characters_to_list(Address)};
|
||||
unicode:characters_to_list(Address);
|
||||
fate_to_erlang({_, _, contract}, {contract, Bin}) ->
|
||||
Address = gmser_api_encoder:encode(contract_pubkey, Bin),
|
||||
{ok, unicode:characters_to_list(Address)};
|
||||
unicode:characters_to_list(Address);
|
||||
fate_to_erlang({_, _, signature}, Bin) ->
|
||||
Address = gmser_api_encoder:encode(signature, Bin),
|
||||
{ok, unicode:characters_to_list(Address)};
|
||||
unicode:characters_to_list(Address);
|
||||
%fate_to_erlang({_, _, channel}, {channel, S}) when is_binary(S) ->
|
||||
%{ok, S};
|
||||
%S;
|
||||
fate_to_erlang({_, _, boolean}, true) ->
|
||||
{ok, true};
|
||||
true;
|
||||
fate_to_erlang({_, _, boolean}, false) ->
|
||||
{ok, false};
|
||||
false;
|
||||
fate_to_erlang({_, _, string}, Bin) ->
|
||||
Str = binary_to_list(Bin),
|
||||
{ok, Str};
|
||||
binary_to_list(Bin);
|
||||
fate_to_erlang({_, _, char}, Val) ->
|
||||
{ok, Val};
|
||||
Val;
|
||||
fate_to_erlang({O, N, {bytes, [Count]}}, {bytes, Bytes}) when is_bitstring(Bytes) ->
|
||||
case check_bytes(O, N, Count, Bytes) of
|
||||
ok -> {ok, Bytes};
|
||||
{error, Reason} -> {error, Reason}
|
||||
ok -> Bytes;
|
||||
{error, Reason} -> erlang:exit(Reason)
|
||||
end;
|
||||
fate_to_erlang({_, _, bits}, {bits, Num}) ->
|
||||
{ok, Num};
|
||||
Num;
|
||||
fate_to_erlang({_, _, {list, [Type]}}, Data) when is_list(Data) ->
|
||||
coerce_list(Type, Data, from_fate);
|
||||
Each = fun(Elem) -> fate_to_erlang(Type, Elem) end,
|
||||
lists:map(Each, Data);
|
||||
fate_to_erlang({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) ->
|
||||
coerce_map(KeyType, ValType, Data, from_fate);
|
||||
fate_to_erlang({O, N, {tuple, ElementTypes}}, {tuple, Data}) ->
|
||||
coerce_map_to_erlang(KeyType, ValType, maps:iterator(Data), #{});
|
||||
fate_to_erlang({_, _, {tuple, ElementTypes}}, {tuple, Data}) ->
|
||||
ElementList = tuple_to_list(Data),
|
||||
coerce_tuple(O, N, ElementTypes, ElementList, from_fate);
|
||||
fate_to_erlang({O, N, {variant, Variants}}, {variant, _, Tag, Tuple}) ->
|
||||
Elems = coerce_elems_to_erlang(ElementTypes, ElementList),
|
||||
list_to_tuple(Elems);
|
||||
fate_to_erlang({_, _, {variant, Variants}}, {variant, _, Tag, Tuple}) ->
|
||||
Terms = tuple_to_list(Tuple),
|
||||
{Name, TermTypes} = lists:nth(Tag + 1, Variants),
|
||||
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, from_fate);
|
||||
fate_to_erlang({O, N, {record, [SingleMemberType]}}, Data) ->
|
||||
{Name, Types} = lists:nth(Tag + 1, Variants),
|
||||
Elems = coerce_elems_to_erlang(Types, Terms),
|
||||
list_to_tuple([Name | Elems]);
|
||||
fate_to_erlang({_, _, {record, [SingleField]}}, Data) ->
|
||||
% Singleton records aren't implemented as FATE tuples at all.
|
||||
% Pretend they are, so we can get the full error indexing of the
|
||||
% non-singletone case.
|
||||
coerce_record_to_map(O, N, [SingleMemberType], {Data});
|
||||
fate_to_erlang({O, N, {record, MemberTypes}}, {tuple, Tuple}) ->
|
||||
coerce_record_to_map(O, N, MemberTypes, Tuple);
|
||||
coerce_record_to_map([SingleField], [Data], #{});
|
||||
fate_to_erlang({_, _, {record, MemberTypes}}, {tuple, Tuple}) ->
|
||||
Terms = tuple_to_list(Tuple),
|
||||
coerce_record_to_map(MemberTypes, Terms, #{});
|
||||
fate_to_erlang({O, N, {unknown_type, _}}, Data) ->
|
||||
case N of
|
||||
already_normalized ->
|
||||
Message = "Warning: Unknown type ~p. Using term ~p as is.~n",
|
||||
io:format(Message, [O, Data]);
|
||||
_ ->
|
||||
Message = "Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n",
|
||||
io:format(Message, [O, N, Data])
|
||||
end,
|
||||
{ok, Data};
|
||||
fate_to_erlang(Type, Data) ->
|
||||
TypeStr = type_to_iolist(Type),
|
||||
io:format("Warning: Could not coerce term into ~s. Using term as is: ~p~n", [TypeStr, Data]),
|
||||
{ok, Data}.
|
||||
warn_unknown_type(O, N, Data),
|
||||
Data;
|
||||
fate_to_erlang({O, N, _}, Data) ->
|
||||
erlang:exit({invalid, O, N, Data}).
|
||||
|
||||
type_to_iolist({O, already_normalized, S}) ->
|
||||
% Already normalized. Example output:
|
||||
% type {map, [string, integer]}
|
||||
opaque_type_to_iolist(O, S);
|
||||
type_to_iolist({O, N, S}) ->
|
||||
% Type alias. Print the alias, and then print the normalized version in
|
||||
% parentheses. Example output:
|
||||
% type "my_alias" (i.e. record type {"my_record_type", [integer]})
|
||||
io_lib:format("type ~p (i.e. ~s)", [O, opaque_type_to_iolist(N, S)]).
|
||||
coerce_elems_to_erlang(Types, Elems) ->
|
||||
Zipped = lists:zip(Types, Elems),
|
||||
Each = fun({Type, Elem}) -> fate_to_erlang(Type, Elem) end,
|
||||
lists:map(Each, Zipped).
|
||||
|
||||
opaque_type_to_iolist(N, {record, _}) ->
|
||||
% N is the name of a record definition.
|
||||
io_lib:format("record type ~p", [N]);
|
||||
opaque_type_to_iolist(N, {variant, _}) ->
|
||||
% N is the name of a variant definition.
|
||||
io_lib:format("variant type ~p", [N]);
|
||||
opaque_type_to_iolist(N, _) ->
|
||||
% N is some other constructive type.
|
||||
io_lib:format("type ~p", [N]).
|
||||
coerce_record_to_map([{Name, Type} | Types], [Term | Terms], Acc) ->
|
||||
Coerced = fate_to_erlang(Type, Term),
|
||||
NewAcc = maps:put(Name, Coerced, Acc),
|
||||
coerce_record_to_map(Types, Terms, NewAcc);
|
||||
coerce_record_to_map([], [], Acc) ->
|
||||
Acc.
|
||||
|
||||
coerce_map_to_erlang(KeyType, ValType, Iter, Acc) ->
|
||||
case maps:next(Iter) of
|
||||
{KeyFATE, ValFATE, Rest} ->
|
||||
Key = fate_to_erlang(KeyType, KeyFATE),
|
||||
Val = fate_to_erlang(ValType, ValFATE),
|
||||
NewAcc = maps:put(Key, Val, Acc),
|
||||
coerce_map_to_erlang(KeyType, ValType, Rest, NewAcc);
|
||||
none ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
|
||||
|
||||
@@ -1360,7 +1312,7 @@ check_erlang_to_fate(Type, Sophia, Fate) ->
|
||||
end.
|
||||
|
||||
check_fate_to_erlang(Type, Fate, Sophia) ->
|
||||
{ok, SophiaActual} = fate_to_erlang(Type, Fate),
|
||||
SophiaActual = fate_to_erlang(Type, Fate),
|
||||
% Now check that the results were what we expected.
|
||||
case SophiaActual of
|
||||
Sophia ->
|
||||
@@ -1525,10 +1477,7 @@ singleton_record_substitution_test() ->
|
||||
{ok, {[], GOutput}} = get_function_signature(AACI, "g"),
|
||||
check_roundtrip(GOutput, #{"it" => #{"it" => 123}}, 123),
|
||||
{ok, {[], HOutput}} = get_function_signature(AACI, "h"),
|
||||
check_roundtrip(HOutput, #{"it" => {123, 456}}, {tuple, {123, 456}}),
|
||||
% Also check that records have accurate paths, since the implementation for
|
||||
% record error paths is a bit fiddly.
|
||||
{error, [{{tuple_too_many_terms, _, _, _}, [{record_element, 0, "it"}]}]} = fate_to_erlang(HOutput, {tuple, {1, 2, 3}}).
|
||||
check_roundtrip(HOutput, #{"it" => {123, 456}}, {tuple, {123, 456}}).
|
||||
|
||||
tuple_substitution_test() ->
|
||||
Contract = "
|
||||
|
||||
Reference in New Issue
Block a user