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
|
% down to the concrete types they actually represent. We annotate each
|
||||||
% subexpression of this concrete type with other info too, in case it helps
|
% subexpression of this concrete type with other info too, in case it helps
|
||||||
% make error messages easier to understand.
|
% 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}.
|
{aaci, Name, Specs, TypeDefs}.
|
||||||
|
|
||||||
@ -1526,34 +1527,39 @@ opaque_type(Params, Pair) when is_map(Pair) ->
|
|||||||
[{Name, TypeArgs}] = maps:to_list(Pair),
|
[{Name, TypeArgs}] = maps:to_list(Pair),
|
||||||
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
||||||
|
|
||||||
% Atoms for builtins, strings (lists) for user-defined types.
|
% Atoms for any builtins that aren't qualified by a namespace in Sophia.
|
||||||
%
|
% Everything else stays as a string, user-defined or not.
|
||||||
% 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).
|
|
||||||
opaque_type_name(<<"int">>) -> integer;
|
opaque_type_name(<<"int">>) -> integer;
|
||||||
opaque_type_name(<<"bool">>) -> boolean;
|
opaque_type_name(<<"bool">>) -> boolean;
|
||||||
opaque_type_name(<<"bits">>) -> bits;
|
opaque_type_name(<<"bits">>) -> bits;
|
||||||
opaque_type_name(<<"char">>) -> char;
|
opaque_type_name(<<"char">>) -> char;
|
||||||
opaque_type_name(<<"string">>) -> string;
|
opaque_type_name(<<"string">>) -> string;
|
||||||
opaque_type_name(<<"address">>) -> address;
|
opaque_type_name(<<"address">>) -> address;
|
||||||
opaque_type_name(<<"hash">>) -> hash;
|
|
||||||
opaque_type_name(<<"signature">>) -> signature;
|
opaque_type_name(<<"signature">>) -> signature;
|
||||||
opaque_type_name(<<"contract">>) -> contract;
|
opaque_type_name(<<"contract">>) -> contract;
|
||||||
opaque_type_name(<<"list">>) -> list;
|
opaque_type_name(<<"list">>) -> list;
|
||||||
opaque_type_name(<<"map">>) -> map;
|
opaque_type_name(<<"map">>) -> map;
|
||||||
opaque_type_name(<<"option">>) -> option;
|
% I'm not sure how to produce channels in Sophia source, but they seem to exist
|
||||||
opaque_type_name(<<"name">>) -> name;
|
% in gmb still.
|
||||||
opaque_type_name(<<"channel">>) -> channel;
|
opaque_type_name(<<"channel">>) -> channel;
|
||||||
opaque_type_name(Name) -> binary_to_list(Name).
|
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
|
% 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
|
% traversed quickly, to take sophia-esque erlang expressions and turn them into
|
||||||
% fate-esque erlang expressions that gmbytecode can serialize. Second, we need
|
% fate-esque erlang expressions that gmbytecode can serialize. Second, we need
|
||||||
@ -1595,6 +1601,10 @@ annotate_type(T, Types) ->
|
|||||||
Error
|
Error
|
||||||
end.
|
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) ->
|
annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) ->
|
||||||
case annotate_type_subexpressions(NExpanded, Types) of
|
case annotate_type_subexpressions(NExpanded, Types) of
|
||||||
{ok, Flat} ->
|
{ok, Flat} ->
|
||||||
@ -1654,23 +1664,17 @@ annotate_variants([], _Types, Acc) ->
|
|||||||
|
|
||||||
% This function evaluates type aliases in a loop, until eventually a usable
|
% This function evaluates type aliases in a loop, until eventually a usable
|
||||||
% definition is found.
|
% 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).
|
normalize_opaque_type(T, Types) -> normalize_opaque_type(T, Types, true).
|
||||||
|
|
||||||
% FIXME detect infinite loops
|
% FIXME detect infinite loops
|
||||||
% FIXME detect builtins with the wrong number of arguments
|
% FIXME detect builtins with the wrong number of arguments
|
||||||
% FIXME should nullary types have an empty list of arguments added before now?
|
% FIXME should nullary types have an empty list of arguments added before now?
|
||||||
normalize_opaque_type({option, [T]}, _Types, IsFirst) ->
|
% These types represent some FATE variant:
|
||||||
% Just like user-made ADTs, 'option' is considered part of the type, and so
|
% Chain.ttl, AENS.pointee, AENS.name, AENSv2.pointee, AENSv2.name,
|
||||||
% options are considered normalised.
|
% Chain.ga_meta_tx, Chain.paying_for_tx, Chain.base_tx,
|
||||||
{ok, IsFirst, {option, [T]}, {variant, [{"None", []}, {"Some", [T]}]}};
|
%
|
||||||
normalize_opaque_type(hash, _Types, IsFirst) ->
|
% And then MCL_BLS12_381.fr represent bytes(32), and MCL_BLS12_381.fp
|
||||||
% For coercion purposes, hash is indistinguishable from bytes(32), so we
|
% represents bytes(48).
|
||||||
% treat it like a type alias.
|
|
||||||
{ok, IsFirst, hash, {bytes, [32]}};
|
|
||||||
normalize_opaque_type(T, _Types, IsFirst) when is_atom(T) ->
|
normalize_opaque_type(T, _Types, IsFirst) when is_atom(T) ->
|
||||||
% Once we have eliminated the above rewrite cases, all other cases are
|
% Once we have eliminated the above rewrite cases, all other cases are
|
||||||
% handled explicitly by the coerce logic, and so are considered normalized.
|
% 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) ->
|
coerce({_, _, signature}, Bin, from_fate) ->
|
||||||
Address = gmser_api_encoder:encode(signature, Bin),
|
Address = gmser_api_encoder:encode(signature, Bin),
|
||||||
{ok, unicode:characters_to_list(Address)};
|
{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, _) ->
|
coerce({_, _, boolean}, true, _) ->
|
||||||
{ok, true};
|
{ok, true};
|
||||||
coerce({_, _, boolean}, "true", _) ->
|
coerce({_, _, boolean}, "true", _) ->
|
||||||
@ -2551,6 +2559,11 @@ coerce_variant_test() ->
|
|||||||
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
|
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
|
||||||
try_coerce(Type, {"B", 456, 789}, {variant, [1, 2], 1, {456, 789}}).
|
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() ->
|
coerce_record_test() ->
|
||||||
{ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}),
|
{ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}),
|
||||||
try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
||||||
@ -2574,7 +2587,7 @@ coerce_unicode_test() ->
|
|||||||
ok.
|
ok.
|
||||||
|
|
||||||
coerce_hash_test() ->
|
coerce_hash_test() ->
|
||||||
{ok, Type} = annotate_type(hash, #{}),
|
{ok, Type} = annotate_type("hash", builtin_typedefs()),
|
||||||
Hash = list_to_binary(lists:seq(1,32)),
|
Hash = list_to_binary(lists:seq(1,32)),
|
||||||
try_coerce(Type, Hash, Hash),
|
try_coerce(Type, Hash, Hash),
|
||||||
ok.
|
ok.
|
||||||
@ -2677,19 +2690,26 @@ obscure_aaci_test() ->
|
|||||||
entrypoint bits(): bits = Bits.all
|
entrypoint bits(): bits = Bits.all
|
||||||
entrypoint character(): char = 'a'
|
entrypoint character(): char = 'a'
|
||||||
entrypoint hash(): hash = #00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF
|
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, AACI} = aaci_from_string(Contract),
|
||||||
|
|
||||||
{ok, {[], {{option, [integer]}, _, _}}} = aaci_lookup_spec(AACI, "options"),
|
|
||||||
|
|
||||||
{ok, {[], {{bytes, [4]}, _, _}}} = aaci_lookup_spec(AACI, "fixed_bytes"),
|
{ok, {[], {{bytes, [4]}, _, _}}} = aaci_lookup_spec(AACI, "fixed_bytes"),
|
||||||
{ok, {[], {{bytes, [any]}, _, _}}} = aaci_lookup_spec(AACI, "any_bytes"),
|
{ok, {[], {{bytes, [any]}, _, _}}} = aaci_lookup_spec(AACI, "any_bytes"),
|
||||||
|
|
||||||
{ok, {[], {bits, _, _}}} = aaci_lookup_spec(AACI, "bits"),
|
{ok, {[], {bits, _, _}}} = aaci_lookup_spec(AACI, "bits"),
|
||||||
|
|
||||||
{ok, {[], {char, _, _}}} = aaci_lookup_spec(AACI, "character"),
|
{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.
|
ok.
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user