Add a map for builtin types
This makes it much easier to implement all these standard library things. In doing so I changed the convention for option, hash, unit, to be stringy rather than atoms. Also I changed some error messages based on what was more helpful during debugging of the unit tests.
This commit is contained in:
parent
7ffc96b68a
commit
057861e9cf
88
src/hz.erl
88
src/hz.erl
@ -1419,7 +1419,8 @@ prepare_aaci(ACI) ->
|
||||
% down to the concrete types they actually represent. We annotate each
|
||||
% subexpression of this concrete type with other info too, in case it helps
|
||||
% make error messages easier to understand.
|
||||
Specs = annotate_function_specs(OpaqueSpecs, TypeDefs, #{}),
|
||||
InternalTypeDefs = maps:merge(builtin_typedefs(), TypeDefs),
|
||||
Specs = annotate_function_specs(OpaqueSpecs, InternalTypeDefs, #{}),
|
||||
|
||||
{aaci, Name, Specs, TypeDefs}.
|
||||
|
||||
@ -1526,34 +1527,39 @@ opaque_type(Params, Pair) when is_map(Pair) ->
|
||||
[{Name, TypeArgs}] = maps:to_list(Pair),
|
||||
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
||||
|
||||
% Atoms for builtins, strings (lists) for user-defined types.
|
||||
%
|
||||
% There are some magic built in types that may or may not also need atoms to
|
||||
% represent them, and may or may not need to be handled explicitly in
|
||||
% coerce/3, if we can't flatten them directly
|
||||
%
|
||||
% These types represent some FATE variant:
|
||||
% Chain.ttl, AENS.pointee, AENS.name, AENSv2.pointee, AENSv2.name,
|
||||
% Chain.ga_meta_tx, Chain.paying_for_tx, Chain.base_tx,
|
||||
%
|
||||
% And then MCL_BLS12_381.fr represent bytes(32), and MCL_BLS12_381.fp
|
||||
% represents bytes(48).
|
||||
% Atoms for any builtins that aren't qualified by a namespace in Sophia.
|
||||
% Everything else stays as a string, user-defined or not.
|
||||
opaque_type_name(<<"int">>) -> integer;
|
||||
opaque_type_name(<<"bool">>) -> boolean;
|
||||
opaque_type_name(<<"bits">>) -> bits;
|
||||
opaque_type_name(<<"char">>) -> char;
|
||||
opaque_type_name(<<"string">>) -> string;
|
||||
opaque_type_name(<<"address">>) -> address;
|
||||
opaque_type_name(<<"hash">>) -> hash;
|
||||
opaque_type_name(<<"signature">>) -> signature;
|
||||
opaque_type_name(<<"contract">>) -> contract;
|
||||
opaque_type_name(<<"list">>) -> list;
|
||||
opaque_type_name(<<"map">>) -> map;
|
||||
opaque_type_name(<<"option">>) -> option;
|
||||
opaque_type_name(<<"name">>) -> name;
|
||||
% I'm not sure how to produce channels in Sophia source, but they seem to exist
|
||||
% in gmb still.
|
||||
opaque_type_name(<<"channel">>) -> channel;
|
||||
opaque_type_name(Name) -> binary_to_list(Name).
|
||||
|
||||
builtin_typedefs() ->
|
||||
#{"unit" => {[], {tuple, []}},
|
||||
"hash" => {[], {bytes, [32]}},
|
||||
"option" => {["'T"], {variant, [{"None", []},
|
||||
{"Some", [{var, "'T"}]}]}},
|
||||
"Chain.ttl" => {[], {variant, [{"FixedTTL", [{list, [integer]}]},
|
||||
{"RelativeTTL", [{list, [integer]}]}]}},
|
||||
"AENS.pointee" => {[], {variant, [{"AccountPt", [{list, [address]}]},
|
||||
{"OraclePt", [{list, [address]}]},
|
||||
{"ContractPt", [{list, [address]}]},
|
||||
{"ChannelPt", [{list, [address]}]}]}},
|
||||
"AENS.name" => {[], {variant, [{"Name", [address,
|
||||
"Chain.ttl",
|
||||
{map, [string, "AENS.pointee"]}]}]}}
|
||||
}.
|
||||
|
||||
% Type preparation has two goals. First, we need a data structure that can be
|
||||
% traversed quickly, to take sophia-esque erlang expressions and turn them into
|
||||
% fate-esque erlang expressions that gmbytecode can serialize. Second, we need
|
||||
@ -1595,6 +1601,10 @@ annotate_type(T, Types) ->
|
||||
Error
|
||||
end.
|
||||
|
||||
annotate_type2(T, _, _, unknown_type, _) ->
|
||||
% If a type is unknown, then it should not be reported as the normalized
|
||||
% name.
|
||||
{ok, {T, unknown_type, unknown_type}};
|
||||
annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) ->
|
||||
case annotate_type_subexpressions(NExpanded, Types) of
|
||||
{ok, Flat} ->
|
||||
@ -1654,23 +1664,17 @@ annotate_variants([], _Types, Acc) ->
|
||||
|
||||
% This function evaluates type aliases in a loop, until eventually a usable
|
||||
% definition is found.
|
||||
%
|
||||
% It also evaluates built-in and standard library types such as options and
|
||||
% names, to their defined variant representation, as well as evaluating
|
||||
% certain binary types like hash, fp, and fr, into their byte representations.
|
||||
normalize_opaque_type(T, Types) -> normalize_opaque_type(T, Types, true).
|
||||
|
||||
% FIXME detect infinite loops
|
||||
% FIXME detect builtins with the wrong number of arguments
|
||||
% FIXME should nullary types have an empty list of arguments added before now?
|
||||
normalize_opaque_type({option, [T]}, _Types, IsFirst) ->
|
||||
% Just like user-made ADTs, 'option' is considered part of the type, and so
|
||||
% options are considered normalised.
|
||||
{ok, IsFirst, {option, [T]}, {variant, [{"None", []}, {"Some", [T]}]}};
|
||||
normalize_opaque_type(hash, _Types, IsFirst) ->
|
||||
% For coercion purposes, hash is indistinguishable from bytes(32), so we
|
||||
% treat it like a type alias.
|
||||
{ok, IsFirst, hash, {bytes, [32]}};
|
||||
% These types represent some FATE variant:
|
||||
% Chain.ttl, AENS.pointee, AENS.name, AENSv2.pointee, AENSv2.name,
|
||||
% Chain.ga_meta_tx, Chain.paying_for_tx, Chain.base_tx,
|
||||
%
|
||||
% And then MCL_BLS12_381.fr represent bytes(32), and MCL_BLS12_381.fp
|
||||
% represents bytes(48).
|
||||
normalize_opaque_type(T, _Types, IsFirst) when is_atom(T) ->
|
||||
% Once we have eliminated the above rewrite cases, all other cases are
|
||||
% handled explicitly by the coerce logic, and so are considered normalized.
|
||||
@ -1825,6 +1829,10 @@ coerce({O, N, signature}, S, to_fate) ->
|
||||
coerce({_, _, signature}, Bin, from_fate) ->
|
||||
Address = gmser_api_encoder:encode(signature, Bin),
|
||||
{ok, unicode:characters_to_list(Address)};
|
||||
%coerce({_, _, channel}, S, to_fate) when is_binary(S) ->
|
||||
%{ok, {channel, S}};
|
||||
%coerce({_, _, channel}, {channel, S}, from_fate) when is_binary(S) ->
|
||||
%{ok, S};
|
||||
coerce({_, _, boolean}, true, _) ->
|
||||
{ok, true};
|
||||
coerce({_, _, boolean}, "true", _) ->
|
||||
@ -2551,6 +2559,11 @@ coerce_variant_test() ->
|
||||
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
|
||||
try_coerce(Type, {"B", 456, 789}, {variant, [1, 2], 1, {456, 789}}).
|
||||
|
||||
coerce_option_test() ->
|
||||
{ok, Type} = annotate_type({"option", [integer]}, builtin_typedefs()),
|
||||
try_coerce(Type, {"None"}, {variant, [0, 1], 0, {}}),
|
||||
try_coerce(Type, {"Some", 1}, {variant, [0, 1], 1, {1}}).
|
||||
|
||||
coerce_record_test() ->
|
||||
{ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}),
|
||||
try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
||||
@ -2574,7 +2587,7 @@ coerce_unicode_test() ->
|
||||
ok.
|
||||
|
||||
coerce_hash_test() ->
|
||||
{ok, Type} = annotate_type(hash, #{}),
|
||||
{ok, Type} = annotate_type("hash", builtin_typedefs()),
|
||||
Hash = list_to_binary(lists:seq(1,32)),
|
||||
try_coerce(Type, Hash, Hash),
|
||||
ok.
|
||||
@ -2677,19 +2690,26 @@ obscure_aaci_test() ->
|
||||
entrypoint bits(): bits = Bits.all
|
||||
entrypoint character(): char = 'a'
|
||||
entrypoint hash(): hash = #00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF
|
||||
entrypoint unit(): unit = ()
|
||||
|
||||
entrypoint ttl(x): Chain.ttl = FixedTTL(x)
|
||||
entrypoint pointee(x): AENS.pointee = AENS.AccountPt(x)
|
||||
entrypoint name(x, y, z): AENS.name = AENS.Name(x, y, z)
|
||||
",
|
||||
{ok, AACI} = aaci_from_string(Contract),
|
||||
|
||||
{ok, {[], {{option, [integer]}, _, _}}} = 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, {[], {char, _, _}}} = aaci_lookup_spec(AACI, "character"),
|
||||
|
||||
{ok, {[], {hash, _, _}}} = aaci_lookup_spec(AACI, "hash"),
|
||||
{ok, {[], {{"option", [integer]}, _, {variant, [{"None", []}, {"Some", [_]}]}}}} = aaci_lookup_spec(AACI, "options"),
|
||||
{ok, {[], {"hash", _, {bytes, [32]}}}} = aaci_lookup_spec(AACI, "hash"),
|
||||
{ok, {[], {"unit", _, {tuple, []}}}} = aaci_lookup_spec(AACI, "unit"),
|
||||
|
||||
{ok, {_, {"Chain.ttl", _, {variant, _}}}} = aaci_lookup_spec(AACI, "ttl"),
|
||||
{ok, {_, {"AENS.pointee", _, {variant, _}}}} = aaci_lookup_spec(AACI, "pointee"),
|
||||
{ok, {_, {"AENS.name", _, {variant, _}}}} = aaci_lookup_spec(AACI, "name"),
|
||||
|
||||
ok.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user