Break up prepare_aaci logic
Now we convert the ACI into trees of opaque types, then flatten the tree into a map and a list of function specs, and only then dereference the types in the function specs down to our accelerated annotated types.
This commit is contained in:
parent
7eb29827a6
commit
ad7be7c8db
190
src/hz.erl
190
src/hz.erl
@ -1405,69 +1405,141 @@ prepare_contract(File) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
prepare_aaci(ACI) ->
|
prepare_aaci(ACI) ->
|
||||||
Types = lists:foldl(fun prepare_namespace_types/2, #{}, ACI),
|
% We want to take the types represented by the ACI, things like N1.T(N2.T),
|
||||||
|
% and dereference them down to concrete types like
|
||||||
|
% {tuple, [integer, string]}. Our type dereferencing algorithms
|
||||||
|
% shouldn't act directly on the JSON-based structures that the compiler
|
||||||
|
% gives us, though, though, so before we do the analysis, we should strip
|
||||||
|
% the ACI down to a list of 'opaque' type defintions and function specs.
|
||||||
|
{Name, OpaqueSpecs, TypeDefs} = convert_aci_types(ACI),
|
||||||
|
|
||||||
|
% Now that we have the opaque types, we can dereference the function specs
|
||||||
|
% down to the concrete types they actually represent.
|
||||||
|
Specs = expand_contract_specs(OpaqueSpecs, TypeDefs, #{}),
|
||||||
|
|
||||||
|
{aaci, Name, Specs, TypeDefs}.
|
||||||
|
|
||||||
|
expand_contract_specs([], _Types, Specs) ->
|
||||||
|
Specs;
|
||||||
|
expand_contract_specs([{Name, ArgsOpaque, ResultOpaque} | Rest], Types, Specs) ->
|
||||||
|
{ok, Args} = flatten_opaque_types(ArgsOpaque, Types, []),
|
||||||
|
{ok, Result} = flatten_opaque_type(ResultOpaque, Types),
|
||||||
|
NewSpecs = maps:put(Name, {Args, Result}, Specs),
|
||||||
|
expand_contract_specs(Rest, Types, NewSpecs).
|
||||||
|
|
||||||
|
convert_aci_types(ACI) ->
|
||||||
|
% Find the main contract, so we can get the specifications of its
|
||||||
|
% entrypoints.
|
||||||
[{NameBin, SpecDefs}] =
|
[{NameBin, SpecDefs}] =
|
||||||
[{N, F}
|
[{N, F}
|
||||||
|| #{contract := #{kind := contract_main,
|
|| #{contract := #{kind := contract_main,
|
||||||
functions := F,
|
functions := F,
|
||||||
name := N}} <- ACI],
|
name := N}} <- ACI],
|
||||||
Name = binary_to_list(NameBin),
|
Name = binary_to_list(NameBin),
|
||||||
Specs = simplify_specs(SpecDefs, #{}, Types),
|
% Turn these specifications into opaque types that we can reason about.
|
||||||
{aaci, Name, Specs, Types}.
|
Specs = lists:map(fun convert_function_spec/1, SpecDefs),
|
||||||
|
|
||||||
prepare_namespace_types(#{namespace := NS}, Types) ->
|
% These specifications can reference other type definitions from the main
|
||||||
prepare_namespace_types2(NS, false, Types);
|
% contract and any other namespaces, so extract these types and convert
|
||||||
prepare_namespace_types(#{contract := NS}, Types) ->
|
% them too.
|
||||||
prepare_namespace_types2(NS, true, Types).
|
TypeDefTree = lists:map(fun convert_namespace_typedefs/1, ACI),
|
||||||
|
% The tree structure of the ACI naturally leads to a tree of opaque types,
|
||||||
|
% but we want a map, so flatten it out before we continue.
|
||||||
|
TypeDefMap = collect_opaque_types(TypeDefTree, #{}),
|
||||||
|
|
||||||
prepare_namespace_types2(NS, IsContract, Types) ->
|
% This is all the information we actually need from the ACI, the rest is
|
||||||
|
% just pre-compute and acceleration.
|
||||||
|
{Name, Specs, TypeDefMap}.
|
||||||
|
|
||||||
|
convert_function_spec(#{name := NameBin, arguments := Args, returns := Result}) ->
|
||||||
|
Name = binary_to_list(NameBin),
|
||||||
|
ArgTypes = lists:map(fun convert_arg/1, Args),
|
||||||
|
ResultType = opaque_type([], Result),
|
||||||
|
{Name, ArgTypes, ResultType}.
|
||||||
|
|
||||||
|
convert_arg(#{name := NameBin, type := TypeDef}) ->
|
||||||
|
Name = binary_to_list(NameBin),
|
||||||
|
{ok, Type} = opaque_type([], TypeDef),
|
||||||
|
{Name, Type}.
|
||||||
|
|
||||||
|
convert_namespace_typedefs(#{namespace := NS}) ->
|
||||||
|
convert_namespace_typedefs2(NS, false);
|
||||||
|
convert_namespace_typedefs(#{contract := NS}) ->
|
||||||
|
convert_namespace_typedefs2(NS, true).
|
||||||
|
|
||||||
|
convert_namespace_typedefs2(NS, IsContract) ->
|
||||||
TypeDefs = maps:get(typedefs, NS),
|
TypeDefs = maps:get(typedefs, NS),
|
||||||
NameBin = maps:get(name, NS),
|
NameBin = maps:get(name, NS),
|
||||||
Name = binary_to_list(NameBin),
|
Name = binary_to_list(NameBin),
|
||||||
Types2 = case IsContract of
|
ContractAsType = case IsContract of
|
||||||
true ->
|
true -> {Name, [], contract};
|
||||||
maps:put(Name, {[], contract}, Types);
|
false -> []
|
||||||
false ->
|
|
||||||
Types
|
|
||||||
end,
|
end,
|
||||||
Types3 = case maps:find(state, NS) of
|
State = case maps:find(state, NS) of
|
||||||
{ok, StateDefACI} ->
|
{ok, StateDefACI} ->
|
||||||
StateDefOpaque = opaque_type([], StateDefACI),
|
StateDefOpaque = opaque_type([], StateDefACI),
|
||||||
maps:put(Name ++ ".state", {[], StateDefOpaque}, Types2);
|
{Name ++ ".state", [], StateDefOpaque};
|
||||||
error ->
|
error ->
|
||||||
Types2
|
[]
|
||||||
end,
|
end,
|
||||||
simplify_typedefs(TypeDefs, Types3, Name ++ ".").
|
ExplicitTypeDefs = convert_explicit_typedefs(TypeDefs, Name ++ ".", []),
|
||||||
|
% Throw all the weird sources of types into one messy deeplist.
|
||||||
|
[ContractAsType, State, ExplicitTypeDefs].
|
||||||
|
|
||||||
simplify_typedefs([], Types, _NamePrefix) ->
|
% The easiest step, turn a deep list of opaque types into a map.
|
||||||
|
collect_opaque_types([], Types) ->
|
||||||
Types;
|
Types;
|
||||||
simplify_typedefs([Next | Rest], Types, NamePrefix) ->
|
collect_opaque_types([L | R], Types) ->
|
||||||
#{name := NameBin, vars := ParamDefs, typedef := T} = Next,
|
NewTypes = collect_opaque_types(L, Types),
|
||||||
|
collect_opaque_types(R, NewTypes);
|
||||||
|
collect_opaque_types({Name, Params, Def}, Types) ->
|
||||||
|
maps:put(Name, {Params, Def}, Types).
|
||||||
|
|
||||||
|
|
||||||
|
convert_explicit_typedefs([], _NamePrefix, Converted) ->
|
||||||
|
Converted;
|
||||||
|
convert_explicit_typedefs([Next | Rest], NamePrefix, Converted) ->
|
||||||
|
#{name := NameBin, vars := ParamDefs, typedef := DefACI} = Next,
|
||||||
Name = NamePrefix ++ binary_to_list(NameBin),
|
Name = NamePrefix ++ binary_to_list(NameBin),
|
||||||
Params = [binary_to_list(Param) || #{name := Param} <- ParamDefs],
|
Params = [binary_to_list(Param) || #{name := Param} <- ParamDefs],
|
||||||
Type = opaque_type(Params, T),
|
Def = opaque_type(Params, DefACI),
|
||||||
NewTypes = maps:put(Name, {Params, Type}, Types),
|
convert_explicit_typedefs(Rest, NamePrefix, [Converted, {Name, Params, Def}]).
|
||||||
simplify_typedefs(Rest, NewTypes, NamePrefix).
|
|
||||||
|
|
||||||
simplify_specs([], Specs, _Types) ->
|
% Convert an ACI type defintion/spec into the 'opaque type' representation that
|
||||||
Specs;
|
% our dereferencing algorithms can reason about.
|
||||||
simplify_specs([Next | Rest], Specs, Types) ->
|
opaque_type(Params, NameBin) when is_binary(NameBin) ->
|
||||||
#{name := NameBin, arguments := ArgDefs, returns := ResultDef} = Next,
|
Name = opaque_type_name(NameBin),
|
||||||
Name = binary_to_list(NameBin),
|
case not is_atom(Name) and lists:member(Name, Params) of
|
||||||
ArgTypes = [simplify_args(Arg, Types) || Arg <- ArgDefs],
|
false -> Name;
|
||||||
{ok, ResultType} = type(ResultDef, Types),
|
true -> {var, Name}
|
||||||
NewSpecs = maps:put(Name, {ArgTypes, ResultType}, Specs),
|
end;
|
||||||
simplify_specs(Rest, NewSpecs, Types).
|
opaque_type(Params, #{record := FieldDefs}) ->
|
||||||
|
Fields = [{binary_to_list(Name), opaque_type(Params, Type)}
|
||||||
|
|| #{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,
|
||||||
|
Variants = lists:map(ConvertVariant, VariantDefs),
|
||||||
|
{variant, Variants};
|
||||||
|
opaque_type(Params, #{tuple := TypeDefs}) ->
|
||||||
|
{tuple, [opaque_type(Params, Type) || Type <- TypeDefs]};
|
||||||
|
opaque_type(Params, Pair) when is_map(Pair) ->
|
||||||
|
[{Name, TypeArgs}] = maps:to_list(Pair),
|
||||||
|
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
||||||
|
|
||||||
simplify_args(#{name := NameBin, type := TypeDef}, Types) ->
|
% atoms for builtins, strings (lists) for user-defined types
|
||||||
Name = binary_to_list(NameBin),
|
opaque_type_name(<<"int">>) -> integer;
|
||||||
% FIXME We should make this error more informative, and continue
|
opaque_type_name(<<"address">>) -> address;
|
||||||
% propogating it up, so that the user can provide their own ACI and find
|
opaque_type_name(<<"contract">>) -> contract;
|
||||||
% out whether it worked or not. At that point ACI -> AACI could almost be a
|
opaque_type_name(<<"bool">>) -> boolean;
|
||||||
% module or package of its own.
|
opaque_type_name(<<"option">>) -> option;
|
||||||
{ok, Type} = type(TypeDef, Types),
|
opaque_type_name(<<"list">>) -> list;
|
||||||
{Name, Type}.
|
opaque_type_name(<<"map">>) -> map;
|
||||||
|
opaque_type_name(<<"string">>) -> string;
|
||||||
|
opaque_type_name(Name) -> binary_to_list(Name).
|
||||||
|
|
||||||
% 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
|
||||||
@ -1494,44 +1566,6 @@ simplify_args(#{name := NameBin, type := TypeDef}, Types) ->
|
|||||||
% can simply render the normalized type expression and know that the error will
|
% can simply render the normalized type expression and know that the error will
|
||||||
% make sense.
|
% make sense.
|
||||||
|
|
||||||
type(T, Types) ->
|
|
||||||
O = opaque_type([], T),
|
|
||||||
flatten_opaque_type(O, Types).
|
|
||||||
|
|
||||||
opaque_type(Params, NameBin) when is_binary(NameBin) ->
|
|
||||||
Name = opaque_type_name(NameBin),
|
|
||||||
case not is_atom(Name) and lists:member(Name, Params) of
|
|
||||||
false -> Name;
|
|
||||||
true -> {var, Name}
|
|
||||||
end;
|
|
||||||
opaque_type(Params, #{record := FieldDefs}) ->
|
|
||||||
Fields = [{binary_to_list(Name), opaque_type(Params, Type)}
|
|
||||||
|| #{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,
|
|
||||||
Variants = lists:map(ConvertVariant, VariantDefs),
|
|
||||||
{variant, Variants};
|
|
||||||
opaque_type(Params, #{tuple := TypeDefs}) ->
|
|
||||||
{tuple, [opaque_type(Params, Type) || Type <- TypeDefs]};
|
|
||||||
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, lists for user defined types
|
|
||||||
opaque_type_name(<<"int">>) -> integer;
|
|
||||||
opaque_type_name(<<"address">>) -> address;
|
|
||||||
opaque_type_name(<<"contract">>) -> contract;
|
|
||||||
opaque_type_name(<<"bool">>) -> boolean;
|
|
||||||
opaque_type_name(<<"option">>) -> option;
|
|
||||||
opaque_type_name(<<"list">>) -> list;
|
|
||||||
opaque_type_name(<<"map">>) -> map;
|
|
||||||
opaque_type_name(<<"string">>) -> string;
|
|
||||||
opaque_type_name(Name) -> binary_to_list(Name).
|
|
||||||
|
|
||||||
flatten_opaque_type(T, Types) ->
|
flatten_opaque_type(T, Types) ->
|
||||||
case normalize_opaque_type(T, Types) of
|
case normalize_opaque_type(T, Types) of
|
||||||
{ok, AlreadyNormalized, NOpaque, NExpanded} ->
|
{ok, AlreadyNormalized, NOpaque, NExpanded} ->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user