diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a4c64..c6c4616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +- Keyword `indexed` is now optional for word typed (`bool`, `int`, `address`, + ...) event arguments. +- State variable pretty printing now produce `'a, 'b, ...` instead of `'1, '2, ...`. +- ACI is restructured and improved: + - `state` and `event` types (if present) now appear at the top level. + - Namespaces and remote interfaces are no longer ignored. + - All type definitions are included in the interface rendering. + - API functions are renamed, new functions are `contract_interface` + and `render_aci_json`. ### Removed ## [3.0.0] - 2019-05-21 diff --git a/docs/aeso_aci.md b/docs/aeso_aci.md index 082aa6b..6a12ff3 100644 --- a/docs/aeso_aci.md +++ b/docs/aeso_aci.md @@ -30,28 +30,14 @@ generates the following JSON structure representing the contract interface: ``` json { "contract": { - "name": "Answers", - "type_defs": [ - { - "name": "state", - "vars": [], - "typedef": "{a : map(string,int)}" - }, - { - "name": "answers", - "vars": [], - "typedef": "map(string,int)" - } - ], "functions": [ { - "name": "init", "arguments": [], - "type": "{a : map(string,int)}", + "name": "init", + "returns": "Answers.state", "stateful": true }, { - "name": "new_answer", "arguments": [ { "name": "q", @@ -62,9 +48,36 @@ generates the following JSON structure representing the contract interface: "type": "int" } ], - "type": "map(string,int)", + "name": "new_answer", + "returns": { + "map": [ + "string", + "int" + ] + }, "stateful": false } + ], + "name": "Answers", + "state": { + "record": [ + { + "name": "a", + "type": "Answers.answers" + } + ] + }, + "type_defs": [ + { + "name": "answers", + "typedef": { + "map": [ + "string", + "int" + ] + }, + "vars": [] + } ] } } @@ -74,62 +87,70 @@ When that encoding is decoded the following include definition is generated: ``` contract Answers = - function new_answer : (string, int) => map(string,int) + record state = {a : Answers.answers} + type answers = map(string, int) + function init : () => Answers.state + function new_answer : (string, int) => map(string, int) ``` ### Types -``` erlang -contract_string() = string() | binary() -json_string() = binary() +```erlang +-type aci_type() :: json | string. +-type json() :: jsx:json_term(). +-type json_text() :: binary(). ``` ### Exports -#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString} +#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()} -Types +Generate the JSON encoding of the interface to a contract. The type definitions +and non-private functions are included in the JSON string. -``` erlang -ConstractString = contract_string() -JSONstring = json_string() -``` +#### render\_aci\_json(json() | json\_text()) -> string(). -Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string. - -#### decode(JSONstring) -> ConstractString. - -Types - -``` erlang -ConstractString = contract_string() -JSONstring = json_string() -``` - -Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract. +Take a JSON encoding of a contract interface and generate a contract interface +that can be included in another contract. ### Example run -This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract. +This is an example of using the ACI generator from an Erlang shell. The file +called `aci_test.aes` contains the contract in the description from which we +want to generate files `aci_test.json` which is the JSON encoding of the +contract interface and `aci_test.include` which is the contract definition to +be included inside another contract. ``` erlang 1> {ok,Contract} = file:read_file("aci_test.aes"). {ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>} -2> {ok,Encoding} = aeso_aci:encode(Contract). -<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>> -3> file:write_file("aci_test.aci", Encoding). +2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract). +{ok,[#{contract => + #{functions => + [#{arguments => [],name => <<"init">>, + returns => <<"Answers.state">>,stateful => true}, + #{arguments => + [#{name => <<"q">>,type => <<"string">>}, + #{name => <<"a">>,type => <<"int">>}], + name => <<"new_answer">>, + returns => #{<<"map">> => [<<"string">>,<<"int">>]}, + stateful => false}], + name => <<"Answers">>, + state => + #{record => + [#{name => <<"a">>,type => <<"Answers.answers">>}]}, + type_defs => + [#{name => <<"answers">>, + typedef => #{<<"map">> => [<<"string">>,<<"int">>]}, + vars => []}]}}]} +3> file:write_file("aci_test.aci", jsx:encode(JsonACI)). ok -4> Decoded = aeso_aci:decode(Encoding). -<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">> -5> file:write_file("aci_test.include", Decoded). +4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI). +{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>} +5> file:write_file("aci_test.include", InterfaceStub). ok -6> jsx:prettify(Encoding). -<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>> +6> jsx:prettify(jsx:encode(JsonACI)). +<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>> ``` -The final call to `jsx:prettify(Encoding)` returns the encoding in a -more easily readable form. This is what is shown in the description -above. - -### Notes - -The ACI generator currently cannot properly handle types defined using `datatype`. +The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a +more easily readable form. This is what is shown in the description above. diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 1db0a01..7e1d2e1 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -9,76 +9,56 @@ -module(aeso_aci). --export([encode/1,encode/2,decode/1]). --export([encode_type/1,encode_stmt/1,encode_expr/1]). +-export([ contract_interface/2 + , contract_interface/3 -%% Define records for the various typed syntactic forms. These make -%% the code easier but don't seem to exist elsewhere. + , render_aci_json/1 -%% Top-level --record(contract, {ann,con,decls}). -%% -record(namespace, {ann,con,decls}). --record(letfun, {ann,id,args,type,body}). --record(type_def, {ann,id,vars,typedef}). + , json_encode_expr/1 + , json_encode_type/1]). -%% Types --record(app_t, {ann,id,fields}). --record(tuple_t, {ann,args}). --record(bytes_t, {ann,len}). --record(record_t, {fields}). --record(field_t, {ann,id,type}). --record(alias_t, {type}). --record(variant_t, {cons}). --record(constr_t, {ann,con,args}). --record(fun_t, {ann,named,args,type}). +-type aci_type() :: json | string. +-type json() :: jsx:json_term(). +-type json_text() :: binary(). -%% Tokens --record(arg, {ann,id,type}). --record(id, {ann,name}). --record(con, {ann,name}). --record(qid, {ann,names}). --record(qcon, {ann,names}). --record(tvar, {ann,name}). +%% External API +-spec contract_interface(aci_type(), string()) -> + {ok, json() | string()} | {error, term()}. +contract_interface(Type, ContractString) -> + contract_interface(Type, ContractString, []). -%% Expressions --record(bool, {ann,bool}). --record(int, {ann,value}). --record(string, {ann,bin}). --record(bytes, {ann,bin}). --record(tuple, {ann,args}). --record(list, {ann,args}). --record(app, {ann,func,args}). --record(typed, {ann,expr,type}). +-spec contract_interface(aci_type(), string(), [term()]) -> + {ok, json() | string()} | {error, term()}. +contract_interface(Type, ContractString, CompilerOpts) -> + do_contract_interface(Type, ContractString, CompilerOpts). -%% encode(ContractString) -> {ok,JSON} | {error,String}. -%% encode(ContractString, Options) -> {ok,JSON} | {error,String}. -%% Build a JSON structure with lists and tuples, not maps, as this -%% allows us to order the fields in the contructed JSON string. +-spec render_aci_json(json() | json_text()) -> {ok, binary()}. +render_aci_json(Json) -> + do_render_aci_json(Json). -encode(ContractString) -> encode(ContractString, []). +-spec json_encode_expr(aeso_syntax:expr()) -> json(). +json_encode_expr(Expr) -> + encode_expr(Expr). -encode(ContractString, Options) when is_binary(ContractString) -> - encode(binary_to_list(ContractString), Options); -encode(ContractString, Options) -> +-spec json_encode_type(aeso_syntax:type()) -> json(). +json_encode_type(Type) -> + encode_type(Type). + +%% Internal functions +do_contract_interface(Type, Contract, Options) when is_binary(Contract) -> + do_contract_interface(Type, binary_to_list(Contract), Options); +do_contract_interface(Type, ContractString, Options) -> try - Ast = parse(ContractString, Options), + Ast = aeso_compiler:parse(ContractString, Options), %% io:format("~p\n", [Ast]), - %% aeso_ast:pp(Ast), - TypedAst = aeso_ast_infer_types:infer(Ast, Options), + TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), %% io:format("~p\n", [TypedAst]), - %% aeso_ast:pp_typed(TypedAst), - %% We find and look at the last contract. - Contract = lists:last(TypedAst), - Cname = contract_name(Contract), - Tdefs = [ encode_typedef(T) || - T <- sort_decls(contract_types(Contract)) ], - Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)), - not is_private_func(F) ], - Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)}, - {<<"type_defs">>, Tdefs}, - {<<"functions">>, Fdefs}]}], - %% io:format("~p\n", [Jmap]), - {ok,jsx:encode(Jmap)} + JArray = [ encode_contract(C) || C <- TypedAst ], + + case Type of + json -> {ok, JArray}; + string -> do_render_aci_json(JArray) + end catch %% The compiler errors. error:{parse_errors, Errors} -> @@ -95,201 +75,208 @@ join_errors(Prefix, Errors, Pfun) -> Ess = [ Pfun(E) || E <- Errors ], list_to_binary(string:join([Prefix|Ess], "\n")). -%% encode_func(Function) -> JSON +encode_contract(Contract) -> + C0 = #{name => encode_name(contract_name(Contract))}, + + Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ], + FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end, + {Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0), + {Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1), + + C1 = C0#{type_defs => Tdefs}, + + C2 = case Es of + [] -> C1; + [#{typedef := ET}] -> C1#{event => ET} + end, + + C3 = case Ss of + [] -> C2; + [#{typedef := ST}] -> C2#{state => ST} + end, + + Fdefs = [ encode_function(F) + || F <- sort_decls(contract_funcs(Contract)), + not is_private(F) ], + + #{contract => C3#{functions => Fdefs}}. + %% Encode a function definition. Currently we are only interested in %% the interface and type. +encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) -> + #{name => encode_name(Name), + arguments => encode_args(Args), + returns => encode_type(Type), + stateful => is_stateful(FDef)}; +encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) -> + #{name => encode_name(Name), + arguments => encode_anon_args(Args), + returns => encode_type(Type), + stateful => is_stateful(FDecl)}. -encode_func(Fdef) -> - Name = function_name(Fdef), - Args = function_args(Fdef), - Type = function_type(Fdef), - [{<<"name">>, encode_name(Name)}, - {<<"arguments">>, encode_args(Args)}, - {<<"returns">>, encode_type(Type)}, - {<<"stateful">>, is_stateful_func(Fdef)}]. +encode_anon_args(Types) -> + Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))], + [ #{name => V, type => encode_type(T)} + || {V, T} <- lists:zip(Anons, Types) ]. -%% encode_args(Args) -> [JSON]. -%% encode_arg(Args) -> JSON. +encode_args(Args) -> [ encode_arg(A) || A <- Args ]. -encode_args(Args) -> - [ encode_arg(A) || A <- Args ]. - -encode_arg(#arg{id=Id,type=T}) -> - [{<<"name">>,encode_type(Id)}, - {<<"type">>,[encode_type(T)]}]. - -%% encode_types(Types) -> [JSON]. -%% encode_type(Type) -> JSON. - -encode_types(Types) -> - [ encode_type(T) || T <- Types ]. - -encode_type(#tvar{name=N}) -> encode_name(N); -encode_type(#id{name=N}) -> encode_name(N); -encode_type(#con{name=N}) -> encode_name(N); -encode_type(#qid{names=Ns}) -> - encode_name(lists:join(".", Ns)); -encode_type(#qcon{names=Ns}) -> - encode_name(lists:join(".", Ns)); %? -encode_type(#tuple_t{args=As}) -> - Eas = encode_types(As), - [{<<"tuple">>,Eas}]; -encode_type(#bytes_t{len=Len}) -> - {<<"bytes">>, Len}; -encode_type(#record_t{fields=Fs}) -> - Efs = encode_fields(Fs), - [{<<"record">>,Efs}]; -encode_type(#app_t{id=Id,fields=Fs}) -> - Name = encode_type(Id), - Efs = encode_types(Fs), - [{Name,Efs}]; -encode_type(#variant_t{cons=Cs}) -> - Ecs = encode_types(Cs), - [{<<"variant">>,Ecs}]; -encode_type(#constr_t{con=C,args=As}) -> - Ec = encode_type(C), - Eas = encode_types(As), - [{Ec,Eas}]; -encode_type(#fun_t{args=As,type=T}) -> - Eas = encode_types(As), - Et = encode_type(T), - [{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}]. - -encode_name(Name) -> - list_to_binary(Name). - -%% encode_fields(Fields) -> [JSON]. -%% encode_field(Field) -> JSON. -%% Encode a record field. - -encode_fields(Fs) -> - [ encode_field(F) || F <- Fs ]. - -encode_field(#field_t{id=Id,type=T}) -> - [{<<"name">>,encode_type(Id)}, - {<<"type">>,[encode_type(T)]}]. - -%% encode_typedef(TypeDef) -> JSON. +encode_arg({arg, _, Id, T}) -> + #{name => encode_type(Id), + type => encode_type(T)}. encode_typedef(Type) -> Name = typedef_name(Type), Vars = typedef_vars(Type), - Def = typedef_def(Type), - [{<<"name">>, encode_name(Name)}, - {<<"vars">>, encode_tvars(Vars)}, - {<<"typedef">>, encode_alias(Def)}]. + Def = typedef_def(Type), + #{name => encode_name(Name), + vars => encode_tvars(Vars), + typedef => encode_type(Def)}. encode_tvars(Vars) -> - [ encode_tvar(V) || V <- Vars ]. + [ #{name => encode_type(V)} || V <- Vars ]. -encode_tvar(#tvar{name=N}) -> - [{<<"name">>, encode_name(N)}]. +%% Encode type +encode_type({tvar, _, N}) -> encode_name(N); +encode_type({id, _, N}) -> encode_name(N); +encode_type({con, _, N}) -> encode_name(N); +encode_type({qid, _, Ns}) -> encode_name(lists:join(".", Ns)); +encode_type({qcon, _, Ns}) -> encode_name(lists:join(".", Ns)); +encode_type({tuple_t, _, As}) -> #{tuple => encode_types(As)}; +encode_type({bytes_t, _, Len}) -> #{bytes => Len}; +encode_type({record_t, Fs}) -> #{record => encode_type_fields(Fs)}; +encode_type({app_t, _, Id, Fs}) -> #{encode_type(Id) => encode_types(Fs)}; +encode_type({variant_t, Cs}) -> #{variant => encode_types(Cs)}; +encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)}; +encode_type({alias_t, Type}) -> encode_type(Type); +encode_type({fun_t, _, _, As, T}) -> #{function => + #{arguments => encode_types(As), + returns => encode_type(T)}}. -encode_alias(#alias_t{type=T}) -> - encode_type(T); -encode_alias(A) -> encode_type(A). +encode_types(Ts) -> [ encode_type(T) || T <- Ts ]. -%% encode_stmt(Stmt) -> JSON. +encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ]. -encode_stmt(E) -> - encode_expr(E). +encode_type_field({field_t, _, Id, T}) -> + #{name => encode_type(Id), + type => encode_type(T)}. -%% encode_exprs(Exprs) -> [JSON]. -%% encode_expr(Expr) -> JSON. +encode_name(Name) when is_list(Name) -> + list_to_binary(Name); +encode_name(Name) when is_binary(Name) -> + Name. -encode_exprs(Es) -> - [ encode_expr(E) || E <- Es ]. +%% Encode Expr +encode_exprs(Es) -> [ encode_expr(E) || E <- Es ]. -encode_expr(#id{name=N}) -> encode_name(N); -encode_expr(#con{name=N}) -> encode_name(N); -encode_expr(#qid{names=Ns}) -> - encode_name(lists:join(".", Ns)); -encode_expr(#qcon{names=Ns}) -> - encode_name(lists:join(".", Ns)); %? -encode_expr(#typed{expr=E}) -> - encode_expr(E); -encode_expr(#bool{bool=B}) -> B; -encode_expr(#int{value=V}) -> V; -encode_expr(#string{bin=B}) -> B; -encode_expr(#bytes{bin=B}) -> B; -encode_expr(#tuple{args=As}) -> - Eas = encode_exprs(As), - [{<<"tuple">>,Eas}]; -encode_expr(#list{args=As}) -> - Eas = encode_exprs(As), - [{<<"list">>,Eas}]; -encode_expr(#app{func=F,args=As}) -> +encode_expr({id, _, N}) -> encode_name(N); +encode_expr({con, _, N}) -> encode_name(N); +encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns)); +encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns)); +encode_expr({typed, _, E}) -> encode_expr(E); +encode_expr({bool, _, B}) -> B; +encode_expr({int, _, V}) -> V; +encode_expr({string, _, S}) -> S; +encode_expr({tuple, _, As}) -> encode_exprs(As); +encode_expr({list, _, As}) -> encode_exprs(As); +encode_expr({bytes, _, B}) -> + Digits = byte_size(B), + <> = B, + list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N]))); +encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id; + Lit == contract_pubkey; Lit == account_pubkey -> + aeser_api_encoder:encode(Lit, L); +encode_expr({app, _, F, As}) -> Ef = encode_expr(F), Eas = encode_exprs(As), - [{<<"apply">>,[{<<"function">>,Ef}, - {<<"arguments">>,Eas}]}]; + #{Ef => Eas}; +encode_expr({record, _, Flds}) -> maps:from_list(encode_fields(Flds)); +encode_expr({map, _, KVs}) -> [ [encode_expr(K), encode_expr(V)] || {K, V} <- KVs ]; encode_expr({Op,_Ann}) -> - list_to_binary(atom_to_list(Op)). + error({encode_expr_todo, Op}). -%% decode(JSON) -> ContractString. -%% Decode a JSON string and generate a suitable contract string which -%% can be included in a contract definition. We decode into a map -%% here as this is easier to work with and order is not important. +encode_fields(Flds) -> [ encode_field(F) || F <- Flds ]. -decode(Json) -> - Map = jsx:decode(Json, [return_maps]), - %% io:format("~p\n", [Map]), - #{<<"contract">> := C} = Map, - list_to_binary(decode_contract(C)). +encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) -> + {encode_name(Fld), encode_expr(Val)}. -decode_contract(#{<<"name">> := Name, - <<"type_defs">> := Ts, - <<"functions">> := Fs}) -> +do_render_aci_json(Json) -> + Contracts = + case Json of + JArray when is_list(JArray) -> JArray; + JObject when is_map(JObject) -> [JObject]; + JText when is_binary(JText) -> + case jsx:decode(Json, [{labels, atom}, return_maps]) of + JArray when is_list(JArray) -> JArray; + JObject when is_map(JObject) -> [JObject]; + _ -> error(bad_aci_json) + end + end, + DecodedContracts = [ decode_contract(C) || #{contract := C} <- Contracts ], + {ok, list_to_binary(string:join(DecodedContracts, "\n"))}. + +decode_contract(#{name := Name, + type_defs := Ts0, + functions := Fs} = C) -> + MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end, + Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++ + [ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0, ["contract"," ",io_lib:format("~s", [Name])," =\n", - decode_tdefs(Ts), - decode_funcs(Fs)]. + decode_tdefs(Ts), decode_funcs(Fs)]. decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. -decode_func(#{<<"name">> := <<"init">>}) -> []; -decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) -> - [" function"," ",io_lib:format("~s", [Name])," : ", - decode_args(As)," => ",decode_type(T),$\n]. +%% decode_func(#{name := init}) -> []; +decode_func(#{name := Name, arguments := As, returns := T}) -> + [" function", " ", io_lib:format("~s", [Name]), " : ", + decode_args(As), " => ", decode_type(T), $\n]. decode_args(As) -> Das = [ decode_arg(A) || A <- As ], [$(,lists:join(", ", Das),$)]. -decode_arg(#{<<"type">> := [T]}) -> decode_type(T). +decode_arg(#{type := T}) -> decode_type(T). decode_types(Ets) -> [ decode_type(Et) || Et <- Ets ]. -decode_type(#{<<"tuple">> := Ets}) -> +decode_type(#{tuple := Ets}) -> Ts = decode_types(Ets), [$(,lists:join(",", Ts),$)]; -decode_type(#{<<"record">> := Efs}) -> +decode_type(#{record := Efs}) -> Fs = decode_fields(Efs), [${,lists:join(",", Fs),$}]; -decode_type(#{<<"list">> := [Et]}) -> +decode_type(#{list := [Et]}) -> T = decode_type(Et), ["list",$(,T,$)]; -decode_type(#{<<"map">> := Ets}) -> +decode_type(#{map := Ets}) -> Ts = decode_types(Ets), ["map",$(,lists:join(",", Ts),$)]; -decode_type(#{<<"variant">> := Ets}) -> +decode_type(#{bytes := Len}) -> + ["bytes(", integer_to_list(Len), ")"]; +decode_type(#{variant := Ets}) -> Ts = decode_types(Ets), lists:join(" | ", Ts); +decode_type(#{function := #{arguments := Args, returns := R}}) -> + [decode_type(#{tuple => Args}), " => ", decode_type(R)]; decode_type(Econs) when is_map(Econs) -> %General constructor [{Ec,Ets}] = maps:to_list(Econs), - C = decode_name(Ec), - Ts = decode_types(Ets), - [C,$(,lists:join(",", Ts),$)]; + AppName = decode_name(Ec), + AppArgs = decode_types(Ets), + case AppArgs of + [] -> [AppName]; + _ -> [AppName,$(,lists:join(", ", AppArgs),$)] + end; decode_type(T) -> %Just raw names. decode_name(T). -decode_name(En) -> - binary_to_list(En). +decode_name(En) when is_atom(En) -> erlang:atom_to_list(En); +decode_name(En) when is_binary(En) -> binary_to_list(En). decode_fields(Efs) -> [ decode_field(Ef) || Ef <- Efs ]. -decode_field(#{<<"name">> := En,<<"type">> := [Et]}) -> +decode_field(#{name := En, type := Et}) -> Name = decode_name(En), Type = decode_type(Et), [Name," : ",Type]. @@ -298,39 +285,41 @@ decode_field(#{<<"name">> := En,<<"type">> := [Et]}) -> %% Here we are only interested in the type definitions and ignore the %% aliases. We find them as they always have variants. -decode_tdefs(Ts) -> [ decode_tdef(T) || - #{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts - ]. +decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ]. -decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) -> - [" datatype"," ",decode_name(Name),decode_tvars(Vs), - " = ",decode_type(T),$\n]. +decode_tdef(#{name := Name, vars := Vs, typedef := T}) -> + TypeDef = decode_type(T), + DefType = decode_deftype(T), + [" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n]. + +decode_deftype(#{record := _Efs}) -> "record"; +decode_deftype(#{variant := _}) -> "datatype"; +decode_deftype(_T) -> "type". decode_tvars([]) -> []; %No tvars, no parentheses decode_tvars(Vs) -> Dvs = [ decode_tvar(V) || V <- Vs ], [$(,lists:join(", ", Dvs),$)]. -decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]). +decode_tvar(#{name := N}) -> io_lib:format("~s", [N]). %% #contract{Ann, Con, [Declarations]}. -contract_name(#contract{con=#con{name=N}}) -> N. +contract_name({contract, _, {con, _, Name}, _}) -> Name; +contract_name({namespace, _, {con, _, Name}, _}) -> Name. -contract_funcs(#contract{decls=Decls}) -> - [ D || D <- Decls, is_record(D, letfun) ]. +contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> + [ D || D <- Decls, is_fun(D)]. -contract_types(#contract{decls=Decls}) -> - [ D || D <- Decls, is_record(D, type_def) ]. +contract_types({C, _, _, Decls}) when C == contract; C == namespace -> + [ D || D <- Decls, is_type(D) ]. -%% To keep dialyzer happy and quiet. -%% namespace_name(#namespace{con=#con{name=N}}) -> N. -%% -%% namespace_funcs(#namespace{decls=Decls}) -> -%% [ D || D <- Decls, is_record(D, letfun) ]. -%% -%% namespace_types(#namespace{decls=Decls}) -> -%% [ D || D <- Decls, is_record(D, type_def) ]. +is_fun({letfun, _, _, _, _, _}) -> true; +is_fun({fun_decl, _, _, _}) -> true; +is_fun(_) -> false. + +is_type({type_def, _, _, _, _}) -> true; +is_type(_) -> false. sort_decls(Ds) -> Sort = fun (D1, D2) -> @@ -339,58 +328,12 @@ sort_decls(Ds) -> end, lists:sort(Sort, Ds). -%% #letfun{Ann, Id, [Arg], Type, Typedef}. -function_name(#letfun{id=#id{name=N}}) -> N. +is_private(Node) -> aeso_syntax:get_ann(private, Node, false). +is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false). -function_args(#letfun{args=Args}) -> Args. +typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name. -function_type(#letfun{type=Type}) -> Type. +typedef_vars({type_def, _, _, Vars, _}) -> Vars. -is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false). - -is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false). - -%% #type_def{Ann, Id, [Var], Typedef}. - -typedef_name(#type_def{id=#id{name=N}}) -> N. - -typedef_vars(#type_def{vars=Vars}) -> Vars. - -typedef_def(#type_def{typedef=Def}) -> Def. - -%% parse(ContractString, Options) -> {ok,AST}. -%% Signal errors, the sophia compiler way. Sigh! - -parse(Text, Options) -> - %% Try and return something sensible here! - case aeso_parser:string(Text, Options) of - %% Yay, it worked! - {ok, Contract} -> Contract; - %% Scan errors. - {error, {Pos, scan_error}} -> - parse_error(Pos, "scan error"); - {error, {Pos, scan_error_no_state}} -> - parse_error(Pos, "scan error"); - %% Parse errors. - {error, {Pos, parse_error, Error}} -> - parse_error(Pos, Error); - {error, {Pos, ambiguous_parse, As}} -> - ErrorString = io_lib:format("Ambiguous ~p", [As]), - parse_error(Pos, ErrorString); - %% Include error - {error, {Pos, include_error, File}} -> - parse_error(Pos, io_lib:format("could not find include file '~s'", [File])) - end. - -parse_error(Pos, ErrorString) -> - %% io:format("Error ~p ~p\n", [Pos,ErrorString]), - Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]), - error({parse_errors, [Error]}). - -pos_error({Line, Pos}) -> - io_lib:format("line ~p, column ~p", [Line, Pos]); -pos_error({no_file, Line, Pos}) -> - pos_error({Line, Pos}); -pos_error({File, Line, Pos}) -> - io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]). +typedef_def({type_def, _, _, _, Def}) -> Def. diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 1e5bfc8..ae6fb16 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -514,7 +514,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. infer(Contracts) -> infer(Contracts, []). --type option() :: return_env. +-type option() :: return_env | dont_unfold. -spec init_env(list(option())) -> env(). init_env(_Options) -> global_env(). @@ -526,7 +526,7 @@ infer(Contracts, Options) -> Env = init_env(Options), create_options(Options), ets_new(type_vars, [set]), - {Env1, Decls} = infer1(Env, Contracts, []), + {Env1, Decls} = infer1(Env, Contracts, [], Options), case proplists:get_value(return_env, Options, false) of false -> Decls; true -> {Env1, Decls} @@ -535,21 +535,22 @@ infer(Contracts, Options) -> clean_up_ets() end. --spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -infer1(Env, [], Acc) -> {Env, lists:reverse(Acc)}; -infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc) -> +-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> + {env(), [aeso_syntax:decl()]}. +infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; +infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) -> %% do type inference on each contract independently. check_scope_name_clash(Env, contract, ConName), - {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code), + {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options), Contract1 = {contract, Ann, ConName, Code1}, Env2 = pop_scope(Env1), Env3 = bind_contract(Contract1, Env2), - infer1(Env3, Rest, [Contract1 | Acc]); -infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc) -> + infer1(Env3, Rest, [Contract1 | Acc], Options); +infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> check_scope_name_clash(Env, namespace, Name), - {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code), + {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), Namespace1 = {namespace, Ann, Name, Code1}, - infer1(pop_scope(Env1), Rest, [Namespace1 | Acc]). + infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options). check_scope_name_clash(Env, Kind, Name) -> case get_scope(Env, qname(Name)) of @@ -560,13 +561,16 @@ check_scope_name_clash(Env, Kind, Name) -> destroy_and_report_type_errors(Env) end. --spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -infer_contract_top(Env, Kind, Defs0) -> +-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) -> + {env(), [aeso_syntax:decl()]}. +infer_contract_top(Env, Kind, Defs0, Options) -> Defs = desugar(Defs0), {Env1, Defs1} = infer_contract(Env, Kind, Defs), - Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), - Defs2 = unfold_record_types(Env2, Defs1), - {Env2, Defs2}. + case proplists:get_value(dont_unfold, Options, false) of + true -> {Env1, Defs1}; + false -> Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), + {Env2, unfold_record_types(Env2, Defs1)} + end. %% TODO: revisit infer_constant({letval, Attrs,_Pattern, Type, E}) -> @@ -611,11 +615,11 @@ infer_contract(Env, What, Defs) -> {Env4, TypeDefs ++ Decls ++ Defs1}. -spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -check_typedefs(Env, Defs) -> +check_typedefs(Env = #env{ namespace = Ns }, Defs) -> create_type_errors(), GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end, TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]), - DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Def) end, TypeMap), + DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Ns, Def) end, TypeMap), SCCs = aeso_utils:scc(DepGraph), {Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []), destroy_and_report_type_errors(Env), @@ -738,25 +742,23 @@ check_event(Env, "event", Ann, Def) -> check_event(_Env, _Name, _Ann, Def) -> Def. check_event_con(Env, {constr_t, Ann, Con, Args}) -> - IsIndexed = fun(T) -> case aeso_syntax:get_ann(indexed, T, false) of - true -> indexed; - false -> notindexed - end end, + IsIndexed = fun(T) -> + T1 = unfold_types_in_type(Env, T), + %% `indexed` is optional but if used it has to be correctly used + case {is_word_type(T1), is_string_type(T1), aeso_syntax:get_ann(indexed, T, false)} of + {true, _, _} -> indexed; + {false, true, true} -> type_error({indexed_type_must_be_word, T, T1}); + {false, true, _} -> notindexed; + {false, false, _} -> type_error({event_arg_type_word_or_string, T, T1}), error + end + end, Indices = lists:map(IsIndexed, Args), Indexed = [ T || T <- Args, IsIndexed(T) == indexed ], NonIndexed = Args -- Indexed, - [ check_event_arg_type(Env, Ix, Type) || {Ix, Type} <- lists:zip(Indices, Args) ], [ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ], [ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ], {constr_t, [{indices, Indices} | Ann], Con, Args}. -check_event_arg_type(Env, Ix, Type0) -> - Type = unfold_types_in_type(Env, Type0), - case Ix of - indexed -> [ type_error({indexed_type_must_be_word, Type0, Type}) || not is_word_type(Type) ]; - notindexed -> [ type_error({payload_type_must_be_string, Type0, Type}) || not is_string_type(Type) ] - end. - %% Not so nice. is_word_type({id, _, Name}) -> lists:member(Name, ["int", "address", "hash", "bits", "bool"]); @@ -1852,8 +1854,8 @@ instantiate(E) -> instantiate1(dereference(E)). instantiate1({uvar, Attr, R}) -> - Next = proplists:get_value(next, ets_lookup(type_vars, next), 1), - TVar = {tvar, Attr, "'" ++ integer_to_list(Next)}, + Next = proplists:get_value(next, ets_lookup(type_vars, next), 0), + TVar = {tvar, Attr, "'" ++ integer_to_tvar(Next)}, ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]), TVar; instantiate1({fun_t, Ann, Named, Args, Ret}) -> @@ -1873,6 +1875,12 @@ instantiate1([A|B]) -> instantiate1(X) -> X. +integer_to_tvar(X) when X < 26 -> + [$a + X]; +integer_to_tvar(X) -> + [integer_to_tvar(X div 26)] ++ [$a + (X rem 26)]. + + %% Save unification failures for error messages. cannot_unify(A, B, When) -> diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 513dac6..48da520 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -18,6 +18,7 @@ , to_sophia_value/4 , to_sophia_value/5 , decode_calldata/3 + , parse/2 ]). -include_lib("aebytecode/include/aeb_opcodes.hrl"). diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index 95b61d2..21c1314 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -6,7 +6,7 @@ %%%------------------------------------------------------------------- -module(aeso_syntax_utils). --export([used_ids/1, used_types/1, used/1]). +-export([used_ids/1, used_types/2, used/1]). -record(alg, {zero, plus, scoped}). @@ -100,8 +100,12 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> used_ids(E) -> [ X || {{term, [X]}, _} <- used(E) ]. -used_types(T) -> - [ X || {{type, [X]}, _} <- used(T) ]. +used_types([Top] = _CurrentNS, T) -> + F = fun({{type, [X]}, _}) -> [X]; + ({{type, [Top1, X]}, _}) when Top1 == Top -> [X]; + (_) -> [] + end, + lists:flatmap(F, used(T)). -type entity() :: {term, [string()]} | {type, [string()]} diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 2ac6480..d586042 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -1,7 +1,7 @@ -module(aeso_abi_tests). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). +-compile([export_all, nowarn_export_all]). -define(SANDBOX(Code), sandbox(fun() -> Code end)). -define(DUMMY_HASH_WORD, 16#123). diff --git a/test/aeso_aci_tests.erl b/test/aeso_aci_tests.erl index 8b5e1d7..2b42135 100644 --- a/test/aeso_aci_tests.erl +++ b/test/aeso_aci_tests.erl @@ -2,31 +2,30 @@ -include_lib("eunit/include/eunit.hrl"). - -do_test() -> - test_contract(1), - test_contract(2), - test_contract(3). +simple_aci_test_() -> + [{"Test contract " ++ integer_to_list(N), + fun() -> test_contract(N) end} + || N <- [1, 2, 3]]. test_contract(N) -> {Contract,MapACI,DecACI} = test_cases(N), - {ok,JSON} = aeso_aci:encode(Contract), - ?assertEqual(MapACI, jsx:decode(JSON, [return_maps])), - ?assertEqual(DecACI, aeso_aci:decode(JSON)). + {ok,JSON} = aeso_aci:contract_interface(json, Contract), + ?assertEqual([MapACI], JSON), + ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). test_cases(1) -> Contract = <<"contract C =\n" " function a(i : int) = i+1\n">>, - MapACI = #{<<"contract">> => - #{<<"name">> => <<"C">>, - <<"type_defs">> => [], - <<"functions">> => - [#{<<"name">> => <<"a">>, - <<"arguments">> => - [#{<<"name">> => <<"i">>, - <<"type">> => [<<"int">>]}], - <<"returns">> => <<"int">>, - <<"stateful">> => false}]}}, + MapACI = #{contract => + #{name => <<"C">>, + type_defs => [], + functions => + [#{name => <<"a">>, + arguments => + [#{name => <<"i">>, + type => <<"int">>}], + returns => <<"int">>, + stateful => false}]}}, DecACI = <<"contract C =\n" " function a : (int) => int\n">>, {Contract,MapACI,DecACI}; @@ -35,42 +34,89 @@ test_cases(2) -> Contract = <<"contract C =\n" " type allan = int\n" " function a(i : allan) = i+1\n">>, - MapACI = #{<<"contract">> => - #{<<"name">> => <<"C">>, - <<"type_defs">> => - [#{<<"name">> => <<"allan">>, - <<"typedef">> => <<"int">>, - <<"vars">> => []}], - <<"functions">> => - [#{<<"arguments">> => - [#{<<"name">> => <<"i">>, - <<"type">> => [<<"int">>]}], - <<"name">> => <<"a">>, - <<"returns">> => <<"int">>, - <<"stateful">> => false}]}}, + MapACI = #{contract => + #{name => <<"C">>, + type_defs => + [#{name => <<"allan">>, + typedef => <<"int">>, + vars => []}], + functions => + [#{arguments => + [#{name => <<"i">>, + type => <<"C.allan">>}], + name => <<"a">>, + returns => <<"int">>, + stateful => false}]}}, DecACI = <<"contract C =\n" - " function a : (int) => int\n">>, + " type allan = int\n" + " function a : (C.allan) => int\n">>, {Contract,MapACI,DecACI}; test_cases(3) -> Contract = <<"contract C =\n" + " type state = ()\n" + " datatype event = SingleEventDefined\n" " datatype bert('a) = Bin('a)\n" " function a(i : bert(string)) = 1\n">>, - MapACI = #{<<"contract">> => - #{<<"functions">> => - [#{<<"arguments">> => - [#{<<"name">> => <<"i">>, - <<"type">> => - [#{<<"C.bert">> => [<<"string">>]}]}], - <<"name">> => <<"a">>,<<"returns">> => <<"int">>, - <<"stateful">> => false}], - <<"name">> => <<"C">>, - <<"type_defs">> => - [#{<<"name">> => <<"bert">>, - <<"typedef">> => - #{<<"variant">> => + MapACI = #{contract => + #{functions => + [#{arguments => + [#{name => <<"i">>, + type => + #{<<"C.bert">> => [<<"string">>]}}], + name => <<"a">>,returns => <<"int">>, + stateful => false}], + name => <<"C">>, + event => #{variant => [#{<<"SingleEventDefined">> => []}]}, + state => #{tuple => []}, + type_defs => + [#{name => <<"bert">>, + typedef => + #{variant => [#{<<"Bin">> => [<<"'a">>]}]}, - <<"vars">> => [#{<<"name">> => <<"'a">>}]}]}}, + vars => [#{name => <<"'a">>}]}]}}, DecACI = <<"contract C =\n" + " type state = ()\n" + " datatype event = SingleEventDefined\n" " datatype bert('a) = Bin('a)\n" " function a : (C.bert(string)) => int\n">>, {Contract,MapACI,DecACI}. + +%% Rounttrip +aci_test_() -> + [{"Testing ACI generation for " ++ ContractName, + fun() -> aci_test_contract(ContractName) end} + || ContractName <- all_contracts()]. + +all_contracts() -> aeso_compiler_tests:compilable_contracts(). + +aci_test_contract(Name) -> + String = aeso_test_utils:read_contract(Name), + Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}], + {ok, JSON} = aeso_aci:contract_interface(json, String, Opts), + + io:format("JSON:\n~p\n", [JSON]), + {ok, ContractStub} = aeso_aci:render_aci_json(JSON), + + io:format("STUB:\n~s\n", [ContractStub]), + check_stub(ContractStub, [{src_file, Name}]), + + ok. + +check_stub(Stub, Options) -> + case aeso_parser:string(binary_to_list(Stub), Options) of + {ok, Ast} -> + try + %% io:format("AST: ~120p\n", [Ast]), + aeso_ast_infer_types:infer(Ast, []) + catch _:{type_errors, TE} -> + io:format("Type error:\n~s\n", [TE]), + error(TE); + _:R -> + io:format("Error: ~p\n", [R]), + error(R) + end; + {error, E} -> + io:format("Error: ~p\n", [E]), + error({parse_error, E}) + end. + diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index b704e21..f6ac15a 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -221,20 +221,15 @@ failing_contracts() -> , {"missing_fields_in_record_expression", [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>, <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>, - <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]} + <<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 40)">>]} , {"namespace_clash", [<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} , {"bad_events", - [<<"The payload type int (at line 10, column 30) should be string">>, - <<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>, - <<"The indexed type string (at line 9, column 25) is not a word type">>, - <<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]} + [<<"The indexed type string (at line 9, column 25) is not a word type">>, + <<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} , {"bad_events2", [<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, - <<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>, - <<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>, - <<"The payload type address (at line 11, column 17) should be string">>, - <<"The payload type int (at line 11, column 26) should be string">>]} + <<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} , {"type_clash", [<<"Cannot unify int\n" " and string\n" @@ -261,46 +256,46 @@ failing_contracts() -> " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" "has the type\n" " address">>, - <<"Cannot unify oracle_query('1, '2)\n" + <<"Cannot unify oracle_query('a, 'b)\n" " and Remote\n" "when checking the type of the expression at line 25, column 5\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" - " oracle_query('1, '2)\n" + " oracle_query('a, 'b)\n" "against the expected type\n" " Remote">>, - <<"Cannot unify oracle_query('3, '4)\n" + <<"Cannot unify oracle_query('c, 'd)\n" " and bytes(32)\n" "when checking the type of the expression at line 23, column 5\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" - " oracle_query('3, '4)\n" + " oracle_query('c, 'd)\n" "against the expected type\n" " bytes(32)">>, - <<"Cannot unify oracle_query('5, '6)\n" + <<"Cannot unify oracle_query('e, 'f)\n" " and oracle(int, bool)\n" "when checking the type of the expression at line 21, column 5\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" - " oracle_query('5, '6)\n" + " oracle_query('e, 'f)\n" "against the expected type\n" " oracle(int, bool)">>, - <<"Cannot unify oracle('7, '8)\n" + <<"Cannot unify oracle('g, 'h)\n" " and Remote\n" "when checking the type of the expression at line 18, column 5\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" - " oracle('7, '8)\n" + " oracle('g, 'h)\n" "against the expected type\n" " Remote">>, - <<"Cannot unify oracle('9, '10)\n" + <<"Cannot unify oracle('i, 'j)\n" " and bytes(32)\n" "when checking the type of the expression at line 16, column 5\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" - " oracle('9, '10)\n" + " oracle('i, 'j)\n" "against the expected type\n" " bytes(32)">>, - <<"Cannot unify oracle('11, '12)\n" + <<"Cannot unify oracle('k, 'l)\n" " and oracle_query(int, bool)\n" "when checking the type of the expression at line 14, column 5\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" - " oracle('11, '12)\n" + " oracle('k, 'l)\n" "against the expected type\n" " oracle_query(int, bool)">>, <<"Cannot unify address\n" diff --git a/test/contracts/bad_events.aes b/test/contracts/bad_events.aes index 5f0d17c..5f9cfec 100644 --- a/test/contracts/bad_events.aes +++ b/test/contracts/bad_events.aes @@ -6,10 +6,8 @@ contract Events = datatype event = Event1(indexed alias_int, indexed int, string) | Event2(alias_string, indexed alias_address) - | BadEvent1(indexed string, string) - | BadEvent2(indexed int, int) - | BadEvent3(indexed alias_string, string) - | BadEvent4(indexed int, alias_address) + | BadEvent1(indexed string) + | BadEvent2(indexed alias_string) function f1(x : int, y : string) = Chain.event(Event1(x, x+1, y)) diff --git a/test/contracts/bad_events2.aes b/test/contracts/bad_events2.aes index 42843f3..02842e3 100644 --- a/test/contracts/bad_events2.aes +++ b/test/contracts/bad_events2.aes @@ -8,7 +8,6 @@ contract Events = | Event2(alias_string, indexed alias_address) | BadEvent1(string, string) | BadEvent2(indexed int, indexed int, indexed int, indexed address) - | BadEvent3(address, int) function f1(x : int, y : string) = Chain.event(Event1(x, x+1, y))