diff --git a/src/hz.erl b/src/hz.erl index 0187fae..8e595f9 100644 --- a/src/hz.erl +++ b/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.