Compare commits
2 Commits
a1be8cbc14
...
057861e9cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
057861e9cf | ||
|
|
7ffc96b68a |
250
src/hz.erl
250
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} ->
|
||||
@ -1652,129 +1662,105 @@ annotate_variants([{Name, Elems} | Rest], Types, Acc) ->
|
||||
annotate_variants([], _Types, Acc) ->
|
||||
{ok, lists:reverse(Acc)}.
|
||||
|
||||
normalize_opaque_type(T, Types) ->
|
||||
case type_is_expanded(T) of
|
||||
false -> normalize_opaque_type(T, Types, true);
|
||||
true -> {ok, true, T, T}
|
||||
end.
|
||||
|
||||
% 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.
|
||||
{ok, IsFirst, T, T};
|
||||
normalize_opaque_type(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.
|
||||
{ok, IsFirst, Type, Type};
|
||||
normalize_opaque_type(T, Types, IsFirst) when is_list(T) ->
|
||||
% Lists/strings indicate userspace types, which may require arg
|
||||
% substitutions. Convert to an explicit but empty arg list, for uniformity.
|
||||
normalize_opaque_type({T, []}, Types, IsFirst);
|
||||
normalize_opaque_type({T, TypeArgs}, Types, IsFirst) when is_list(T) ->
|
||||
case maps:find(T, Types) of
|
||||
%{error, invalid_aci}; % FIXME more info
|
||||
error ->
|
||||
{ok, IsFirst, {T, TypeArgs}, {unknown_type, TypeArgs}};
|
||||
% We couldn't find this named type... Keep building the AACI, but
|
||||
% mark this type expression as unknown, so that FATE coercions
|
||||
% aren't attempted.
|
||||
{ok, IsFirst, {T, TypeArgs}, unknown_type};
|
||||
{ok, {TypeParamNames, Definition}} ->
|
||||
Bindings = lists:zip(TypeParamNames, TypeArgs),
|
||||
normalize_opaque_type2(T, TypeArgs, Types, IsFirst, Bindings, Definition)
|
||||
% We have a definition for this type, including names for whatever
|
||||
% args we have been given. Subtitute our args into this.
|
||||
NewType = substitute_opaque_type(TypeParamNames, Definition, TypeArgs),
|
||||
% Now continue on to see if we need to restart the loop or not.
|
||||
normalize_opaque_type2(IsFirst, {T, TypeArgs}, NewType, Types)
|
||||
end.
|
||||
|
||||
normalize_opaque_type2(T, TypeArgs, Types, IsFirst, Bindings, Definition) ->
|
||||
SubResult =
|
||||
case Bindings of
|
||||
[] -> {ok, Definition};
|
||||
_ -> substitute_opaque_type(Bindings, Definition)
|
||||
end,
|
||||
case SubResult of
|
||||
% Type names were already normalized if they were ADTs or records,
|
||||
% since for those connectives the name is considered part of the type.
|
||||
{ok, NextT = {variant, _}} ->
|
||||
{ok, IsFirst, {T, TypeArgs}, NextT};
|
||||
{ok, NextT = {record, _}} ->
|
||||
{ok, IsFirst, {T, TypeArgs}, NextT};
|
||||
% Everything else has to be substituted down to a built-in connective
|
||||
% to be considered normalized.
|
||||
{ok, NextT} ->
|
||||
normalize_opaque_type3(NextT, Types);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
normalize_opaque_type2(IsFirst, PrevType, NextType = {variant, _}, _) ->
|
||||
% We have reduced to a variant. Report the type name as the normalized
|
||||
% type, but also provide the variant definition itself as the candidate
|
||||
% flattened type for further annotation.
|
||||
{ok, IsFirst, PrevType, NextType};
|
||||
normalize_opaque_type2(IsFirst, PrevType, NextType = {record, _}, _) ->
|
||||
% We have reduced to a record. Report the type name as the normalized
|
||||
% type, but also provide the record definition itself as the candidate
|
||||
% flattened type for further annotation.
|
||||
{ok, IsFirst, PrevType, NextType};
|
||||
normalize_opaque_type2(_, _, NextType, Types) ->
|
||||
% Not a variant or record yet, so go back to the start of the loop.
|
||||
% It will no longer be the first iteration.
|
||||
normalize_opaque_type(NextType, Types, false).
|
||||
|
||||
% while this does look like normalize_opaque_type/2, it sets IsFirst to false
|
||||
% instead of true, and is part of the loop, instead of being an initial
|
||||
% condition for the loop.
|
||||
normalize_opaque_type3(NextT, Types) ->
|
||||
case type_is_expanded(NextT) of
|
||||
false -> normalize_opaque_type(NextT, Types, false);
|
||||
true -> {ok, false, NextT, NextT}
|
||||
end.
|
||||
% Perform a beta-reduction on a type expression.
|
||||
substitute_opaque_type([], Definition, _) ->
|
||||
% There are no parameters to substitute. This is the simplest way of
|
||||
% defining type aliases, records, and variants, so we should make sure to
|
||||
% short circuit all the recursive descent logic, since it won't actually
|
||||
% do anything.
|
||||
Definition;
|
||||
substitute_opaque_type(TypeParamNames, Definition, TypeArgs) ->
|
||||
% Bundle the param names alongside the args that we want to substitute, so
|
||||
% that we can keyfind the one list.
|
||||
Bindings = lists:zip(TypeParamNames, TypeArgs),
|
||||
substitute_opaque_type(Bindings, Definition).
|
||||
|
||||
% Strings indicate names that should be substituted. Atoms indicate built in
|
||||
% types, which don't need to be expanded, except for option.
|
||||
% TODO: Stop calling this, so that we can stop redundantly enumerating all the
|
||||
% built in types.
|
||||
type_is_expanded({option, _}) -> false;
|
||||
type_is_expanded(hash) -> false;
|
||||
type_is_expanded(X) when is_atom(X) -> true;
|
||||
type_is_expanded({X, _}) when is_atom(X) -> true;
|
||||
type_is_expanded(_) -> false.
|
||||
|
||||
% Skip traversal if there is nothing to substitute. This will often be the
|
||||
% most common case.
|
||||
substitute_opaque_type(Bindings, {var, VarName}) ->
|
||||
case lists:keyfind(VarName, 1, Bindings) of
|
||||
false -> {error, invalid_aci};
|
||||
{_, TypeArg} -> {ok, TypeArg}
|
||||
end;
|
||||
substitute_opaque_type(Bindings, {variant, Args}) ->
|
||||
case substitute_variant_types(Bindings, Args, []) of
|
||||
{ok, Result} -> {ok, {variant, Result}};
|
||||
Error -> Error
|
||||
end;
|
||||
substitute_opaque_type(Bindings, {record, Args}) ->
|
||||
case substitute_record_types(Bindings, Args, []) of
|
||||
{ok, Result} -> {ok, {record, Result}};
|
||||
Error -> Error
|
||||
{_, TypeArg} -> TypeArg;
|
||||
% No valid ACI will create this case. Regardless, the user should
|
||||
% still be able to specify arbitrary gmb FATE terms for whatever this
|
||||
% is meant to be.
|
||||
false -> unknown_type
|
||||
end;
|
||||
substitute_opaque_type(Bindings, {variant, Variants}) ->
|
||||
Each = fun({VariantName, Elements}) ->
|
||||
NewElements = substitute_opaque_types(Bindings, Elements),
|
||||
{VariantName, NewElements}
|
||||
end,
|
||||
NewVariants = lists:map(Each, Variants),
|
||||
{variant, NewVariants};
|
||||
substitute_opaque_type(Bindings, {record, Fields}) ->
|
||||
Each = fun({FieldName, FieldType}) ->
|
||||
NewType = substitute_opaque_type(Bindings, FieldType),
|
||||
{FieldName, NewType}
|
||||
end,
|
||||
NewFields = lists:map(Each, Fields),
|
||||
{record, NewFields};
|
||||
substitute_opaque_type(Bindings, {Connective, Args}) ->
|
||||
case substitute_opaque_types(Bindings, Args, []) of
|
||||
{ok, Result} -> {ok, {Connective, Result}};
|
||||
Error -> Error
|
||||
end;
|
||||
NewArgs = substitute_opaque_types(Bindings, Args),
|
||||
{Connective, NewArgs};
|
||||
substitute_opaque_type(_Bindings, Type) ->
|
||||
{ok, Type}.
|
||||
Type.
|
||||
|
||||
substitute_variant_types(Bindings, [{VariantName, Elements} | Rest], Acc) ->
|
||||
case substitute_opaque_types(Bindings, Elements, []) of
|
||||
{ok, Result} -> substitute_variant_types(Bindings, Rest, [{VariantName, Result} | Acc]);
|
||||
Error -> Error
|
||||
end;
|
||||
substitute_variant_types(_Bindings, [], Acc) ->
|
||||
{ok, lists:reverse(Acc)}.
|
||||
|
||||
substitute_record_types(Bindings, [{ElementName, Type} | Rest], Acc) ->
|
||||
case substitute_opaque_type(Bindings, Type) of
|
||||
{ok, Result} -> substitute_record_types(Bindings, Rest, [{ElementName, Result} | Acc]);
|
||||
Error -> Error
|
||||
end;
|
||||
substitute_record_types(_Bindings, [], Acc) ->
|
||||
{ok, lists:reverse(Acc)}.
|
||||
|
||||
substitute_opaque_types(Bindings, [Next | Rest], Acc) ->
|
||||
case substitute_opaque_type(Bindings, Next) of
|
||||
{ok, Result} -> substitute_opaque_types(Bindings, Rest, [Result | Acc]);
|
||||
Error -> Error
|
||||
end;
|
||||
substitute_opaque_types(_Bindings, [], Acc) ->
|
||||
{ok, lists:reverse(Acc)}.
|
||||
substitute_opaque_types(Bindings, Types) ->
|
||||
Each = fun(Type) -> substitute_opaque_type(Bindings, Type) end,
|
||||
lists:map(Each, Types).
|
||||
|
||||
coerce_bindings(VarTypes, Terms, Direction) ->
|
||||
DefLength = length(VarTypes),
|
||||
@ -1843,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", _) ->
|
||||
@ -2569,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}}).
|
||||
@ -2592,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.
|
||||
@ -2695,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