diff --git a/src/hz.erl b/src/hz.erl index 55fb64c..d330a11 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1405,69 +1405,141 @@ prepare_contract(File) -> end. 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}] = [{N, F} || #{contract := #{kind := contract_main, functions := F, name := N}} <- ACI], Name = binary_to_list(NameBin), - Specs = simplify_specs(SpecDefs, #{}, Types), - {aaci, Name, Specs, Types}. + % Turn these specifications into opaque types that we can reason about. + Specs = lists:map(fun convert_function_spec/1, SpecDefs), -prepare_namespace_types(#{namespace := NS}, Types) -> - prepare_namespace_types2(NS, false, Types); -prepare_namespace_types(#{contract := NS}, Types) -> - prepare_namespace_types2(NS, true, Types). + % These specifications can reference other type definitions from the main + % contract and any other namespaces, so extract these types and convert + % them too. + 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), NameBin = maps:get(name, NS), Name = binary_to_list(NameBin), - Types2 = case IsContract of - true -> - maps:put(Name, {[], contract}, Types); - false -> - Types - end, - Types3 = case maps:find(state, NS) of - {ok, StateDefACI} -> - StateDefOpaque = opaque_type([], StateDefACI), - maps:put(Name ++ ".state", {[], StateDefOpaque}, Types2); - error -> - Types2 - end, - simplify_typedefs(TypeDefs, Types3, Name ++ "."). + ContractAsType = case IsContract of + true -> {Name, [], contract}; + false -> [] + end, + State = case maps:find(state, NS) of + {ok, StateDefACI} -> + StateDefOpaque = opaque_type([], StateDefACI), + {Name ++ ".state", [], StateDefOpaque}; + error -> + [] + end, + 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; -simplify_typedefs([Next | Rest], Types, NamePrefix) -> - #{name := NameBin, vars := ParamDefs, typedef := T} = Next, +collect_opaque_types([L | R], Types) -> + 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), Params = [binary_to_list(Param) || #{name := Param} <- ParamDefs], - Type = opaque_type(Params, T), - NewTypes = maps:put(Name, {Params, Type}, Types), - simplify_typedefs(Rest, NewTypes, NamePrefix). + Def = opaque_type(Params, DefACI), + convert_explicit_typedefs(Rest, NamePrefix, [Converted, {Name, Params, Def}]). -simplify_specs([], Specs, _Types) -> - Specs; -simplify_specs([Next | Rest], Specs, Types) -> - #{name := NameBin, arguments := ArgDefs, returns := ResultDef} = Next, - Name = binary_to_list(NameBin), - ArgTypes = [simplify_args(Arg, Types) || Arg <- ArgDefs], - {ok, ResultType} = type(ResultDef, Types), - NewSpecs = maps:put(Name, {ArgTypes, ResultType}, Specs), - simplify_specs(Rest, NewSpecs, Types). +% Convert an ACI type defintion/spec into the 'opaque type' representation that +% our dereferencing algorithms can reason about. +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]}. -simplify_args(#{name := NameBin, type := TypeDef}, Types) -> - Name = binary_to_list(NameBin), - % FIXME We should make this error more informative, and continue - % propogating it up, so that the user can provide their own ACI and find - % out whether it worked or not. At that point ACI -> AACI could almost be a - % module or package of its own. - {ok, Type} = type(TypeDef, Types), - {Name, Type}. +% atoms for builtins, strings (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). % 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 @@ -1494,44 +1566,6 @@ simplify_args(#{name := NameBin, type := TypeDef}, Types) -> % can simply render the normalized type expression and know that the error will % 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) -> case normalize_opaque_type(T, Types) of {ok, AlreadyNormalized, NOpaque, NExpanded} ->