Proper checking of types

This commit is contained in:
Ulf Norell 2019-02-04 18:01:47 +01:00
parent dfa286d43c
commit d9188d58a7
6 changed files with 187 additions and 54 deletions

View File

@ -95,6 +95,7 @@
-record(env,
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
, vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
, namespace = [] :: qname()
}).
@ -135,6 +136,18 @@ bind_vars([], Env) -> Env;
bind_vars([{X, T} | Vars], Env) ->
bind_vars(Vars, bind_var(X, T, Env)).
-spec bind_tvars([aeso_syntax:tvar()], env()) -> env().
bind_tvars(Xs, Env) ->
Env#env{ typevars = [X || {tvar, _, X} <- Xs] }.
-spec check_tvar(env(), aeso_syntax:tvar()) -> aeso_syntax:tvar() | no_return().
check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) ->
case TVars == unrestricted orelse lists:member(X, TVars) of
true -> ok;
false -> type_error({unbound_type_variable, T})
end,
T.
-spec bind_fun(name(), type() | typesig(), env()) -> env().
bind_fun(X, Type, Env) ->
Ann = aeso_syntax:get_ann(Type),
@ -159,6 +172,19 @@ bind_type(X, Ann, Def, Env) ->
Scope#scope{ types = [{X, {Ann, Def}} | Types] }
end).
%% Bind state primitives
-spec bind_state(env()) -> env().
bind_state(Env) ->
Ann = [{origin, system}],
Unit = {tuple_t, Ann, []},
State =
case lookup_type(Env, {id, Ann, "state"}) of
{S, _} -> {qid, Ann, S};
false -> Unit
end,
bind_funs([{"state", State},
{"put", {fun_t, Ann, [], [State], Unit}}], Env).
-spec bind_field(name(), field_info(), env()) -> env().
bind_field(X, Info, Env = #env{ fields = Fields }) ->
Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields),
@ -300,7 +326,6 @@ global_env() ->
String = {id, Ann, "string"},
Address = {id, Ann, "address"},
Event = {id, Ann, "event"},
State = {id, Ann, "state"},
Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"},
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
@ -331,9 +356,6 @@ global_env() ->
%% TTL constructors
{"RelativeTTL", Fun1(Int, TTL)},
{"FixedTTL", Fun1(Int, TTL)},
%% State
{"state", State},
{"put", Fun1(State, Unit)},
%% Abort
{"abort", Fun1(String, A)}])
, types = MkDefs(
@ -356,7 +378,7 @@ global_env() ->
{"difficulty", Int},
{"gas_limit", Int},
{"event", Fun1(Event, Unit)}])
, types = MkDefs([{"TTL", 0}]) },
, types = MkDefs([{"ttl", 0}]) },
ContractScope = #scope
{ funs = MkDefs(
@ -488,17 +510,17 @@ infer(Contracts, Options) ->
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()]) -> [aeso_syntax:decl()].
infer1(_, [], Acc) -> lists:reverse(Acc);
infer1(Env, [Contract = {contract, Ann, ConName, Code} | Rest], Acc) ->
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc) ->
%% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), Code),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code),
Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract, Env2),
Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc]);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc) ->
check_scope_name_clash(Env, namespace, Name),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), Code),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code),
Namespace1 = {namespace, Ann, Name, Code1},
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc]).
@ -508,13 +530,13 @@ check_scope_name_clash(Env, Kind, Name) ->
#scope{ kind = K, ann = Ann } ->
create_type_errors(),
type_error({duplicate_scope, Kind, Name, K, Ann}),
destroy_and_report_type_errors()
destroy_and_report_type_errors(Env)
end.
-spec infer_contract_top(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Defs0) ->
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0) ->
Defs = desugar(Defs0),
{Env1, Defs1} = infer_contract(Env, Defs),
{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}.
@ -528,17 +550,22 @@ infer_constant({letval, Attrs,_Pattern, Type, E}) ->
%% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions.
-spec infer_contract(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env, Defs) ->
-spec infer_contract(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env, What, Defs) ->
Kind = fun({type_def, _, _, _, _}) -> type;
({letfun, _, _, _, _, _}) -> function;
({fun_decl, _, _, _}) -> prototype
end,
Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end,
{Env1, TypeDefs} = check_typedefs(Env, Get(type)),
ProtoSigs = [ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ],
Env2 =
case What of
namespace -> Env1;
contract -> bind_state(Env1) %% bind state and put
end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
create_type_errors(),
Env2 = bind_funs(ProtoSigs, Env1),
Env3 = bind_funs(ProtoSigs, Env2),
Functions = Get(function),
%% Check for duplicates in Functions (we turn it into a map below)
_ = bind_funs([{Fun, {tuple_t, Ann, []}} || {letfun, Ann, {id, _, Fun}, _, _, _} <- Functions],
@ -548,9 +575,9 @@ infer_contract(Env, Defs) ->
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
{Env3, Defs1} = check_sccs(Env2, FunMap, SCCs, []),
destroy_and_report_type_errors(),
{Env3, TypeDefs ++ Defs1}.
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
destroy_and_report_type_errors(Env4),
{Env4, TypeDefs ++ Decls ++ Defs1}.
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env, Defs) ->
@ -560,7 +587,7 @@ check_typedefs(Env, Defs) ->
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Def) end, TypeMap),
SCCs = aeso_utils:scc(DepGraph),
{Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []),
destroy_and_report_type_errors(),
destroy_and_report_type_errors(Env),
{Env1, Defs1}.
-spec check_typedef_sccs(env(), #{ name() => aeso_syntax:decl() },
@ -571,17 +598,18 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
case maps:get(Name, TypeMap, undefined) of
undefined -> check_typedef_sccs(Env, TypeMap, SCCs, Acc); %% Builtin type
{type_def, Ann, D, Xs, Def0} ->
Def = check_event(Env, Name, Ann, Def0),
Def = check_event(Env, Name, Ann, check_typedef(bind_tvars(Xs, Env), Def0)),
Acc1 = [{type_def, Ann, D, Xs, Def} | Acc],
Env1 = bind_type(Name, Ann, {Xs, Def}, Env),
case Def of
{alias_t, _} -> check_typedef_sccs(Env1, TypeMap, SCCs, Acc1); %% TODO: check these
{alias_t, _} -> check_typedef_sccs(Env1, TypeMap, SCCs, Acc1);
{record_t, Fields} ->
RecTy = app_t(Ann, D, Xs),
%% check_type to get qualified name
RecTy = check_type(Env1, app_t(Ann, D, Xs)),
Env2 = check_fields(Env1, TypeMap, RecTy, Fields),
check_typedef_sccs(Env2, TypeMap, SCCs, Acc1);
{variant_t, Cons} ->
Target = app_t(Ann, D, Xs),
Target = check_type(Env1, app_t(Ann, D, Xs)),
ConType = fun([]) -> Target; (Args) -> {type_sig, Ann, [], Args, Target} end,
ConTypes = [ begin
{constr_t, _, {con, _, Con}, Args} = ConDef,
@ -597,6 +625,65 @@ check_typedef_sccs(Env, TypeMap, [{cyclic, Names} | SCCs], Acc) ->
type_error({recursive_types_not_implemented, lists:map(Id, Names)}),
check_typedef_sccs(Env, TypeMap, SCCs, Acc).
-spec check_typedef(env(), aeso_syntax:typedef()) -> aeso_syntax:typedef().
check_typedef(Env, {alias_t, Type}) ->
{alias_t, check_type(Env, Type)};
check_typedef(Env, {record_t, Fields}) ->
{record_t, [ {field_t, Ann, Id, check_type(Env, Type)} || {field_t, Ann, Id, Type} <- Fields ]};
check_typedef(Env, {variant_t, Cons}) ->
{variant_t, [ {constr_t, Ann, Con, [ check_type(Env, Arg) || Arg <- Args ]}
|| {constr_t, Ann, Con, Args} <- Cons ]}.
-spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type().
check_type(Env, T) ->
check_type(Env, T, 0).
%% Check a type against an arity.
-spec check_type(env(), utype(), non_neg_integer()) -> utype().
check_type(Env, T = {tvar, _, _}, Arity) ->
[ type_error({higher_kinded_typevar, T}) || Arity /= 0 ],
check_tvar(Env, T);
check_type(_Env, X = {id, _, "_"}, Arity) ->
ensure_base_type(X, Arity),
X;
check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id; Tag == qid ->
case lookup_type(Env, X) of
{Q, {_, Def}} ->
Arity1 = case Def of
{builtin, Ar} -> Ar;
{Args, _} -> length(Args)
end,
[ type_error({wrong_type_arguments, X, Arity, Arity1}) || Arity /= Arity1 ],
set_qname(Q, X);
false -> type_error({unbound_type, X}), X
end;
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
ensure_base_type(Type, Arity),
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
Types1 = [ check_type(Env, T, 0) || T <- Types ],
Type1 = check_type(Env, Type, Arity + length(Types)),
{app_t, Ann, Type1, Types1};
check_type(Env, Type = {fun_t, Ann, NamedArgs, Args, Ret}, Arity) ->
ensure_base_type(Type, Arity),
NamedArgs1 = [ check_named_arg(Env, NamedArg) || NamedArg <- NamedArgs ],
Args1 = [ check_type(Env, Arg, 0) || Arg <- Args ],
Ret1 = check_type(Env, Ret, 0),
{fun_t, Ann, NamedArgs1, Args1, Ret1};
check_type(_Env, Type = {uvar, _, _}, Arity) ->
ensure_base_type(Type, Arity),
Type.
ensure_base_type(Type, Arity) ->
[ type_error({wrong_type_arguments, Type, Arity, 0}) || Arity /= 0 ],
ok.
-spec check_named_arg(env(), aeso_syntax:named_arg_t()) -> aeso_syntax:named_arg_t().
check_named_arg(Env, {named_arg_t, Ann, Id, Type, Default}) ->
Type1 = check_type(Env, Type),
{typed, _, Default1, _} = check_expr(Env, Default, Type1),
{named_arg_t, Ann, Id, Type1, Default1}.
-spec check_fields(env(), #{ name() => aeso_syntax:decl() }, type(), [aeso_syntax:field_t()]) -> env().
check_fields(Env, _TypeMap, _, []) -> Env;
check_fields(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) ->
@ -622,7 +709,6 @@ check_event_con(Env, {constr_t, Ann, Con, Args}) ->
Indexed = [ T || T <- Args, IsIndexed(T) == indexed ],
NonIndexed = Args -- Indexed,
[ check_event_arg_type(Env, Ix, Type) || {Ix, Type} <- lists:zip(Indices, Args) ],
%% TODO: Is is possible to check also the types of arguments in a sensible way?
[ 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}.
@ -690,9 +776,10 @@ check_reserved_entrypoints(Funs) ->
|| {Name, Def} <- maps:to_list(Funs), lists:member(Name, Reserved) ],
ok.
-spec check_fundecl(env(), aeso_syntax:decl()) -> {name(), typesig()}.
check_fundecl(_Env, {fun_decl, Ann, {id, _, Name}, {fun_t, _, Named, Args, Ret}}) ->
{Name, {type_sig, Ann, Named, Args, Ret}}; %% TODO: actually check that the type makes sense!
-spec check_fundecl(env(), aeso_syntax:decl()) -> {{name(), typesig()}, aeso_syntax:decl()}.
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type),
{{Name, {type_sig, Ann, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}};
check_fundecl(_, {fun_decl, _Attrib, {id, _, Name}, Type}) ->
error({fundecl_must_have_funtype, Name, Type}).
@ -712,8 +799,8 @@ check_special_funs(Env, {{"init", Type}, _}) ->
State =
%% We might have implicit (no) state.
case lookup_type(Env, {id, [], "state"}) of
false -> {tuple_t, [{origin, system}], []};
_ -> {id, [{origin, system}], "state"}
false -> {tuple_t, [{origin, system}], []};
{S, _} -> {qid, [], S}
end,
unify(Env, Res, State, {checking_init_type, Ann});
check_special_funs(_, _) -> ok.
@ -743,8 +830,8 @@ infer_letrec(Env, {letrec, Attrs, Defs}) ->
{TypeSigs, {letrec, Attrs, NewDefs}}.
infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
ArgTypes = [{ArgName, arg_type(T)} || {arg, _, ArgName, T} <- Args],
ExpectedType = arg_type(What),
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
ExpectedType = check_type(Env, arg_type(What)),
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
NewArgs = [{arg, A1, {id, A2, ArgName}, T}
|| {{_, T}, {arg, A1, {id, A2, ArgName}, _}} <- lists:zip(ArgTypes, Args)],
@ -822,7 +909,8 @@ infer_expr(Env, {list, As, Elems}) ->
NewElems = [check_expr(Env, X, ElemType) || X <- Elems],
{typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}};
infer_expr(Env, {typed, As, Body, Type}) ->
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type),
Type1 = check_type(Env, Type),
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
{typed, As, NewBody, NewType};
infer_expr(Env, {app, Ann, Fun, Args0}) ->
%% TODO: fix parser to give proper annotation for normal applications!
@ -925,7 +1013,7 @@ infer_expr(Env, {block, Attrs, Stmts}) ->
{typed, Attrs, {block, Attrs, NewStmts}, BlockType};
infer_expr(Env, {lam, Attrs, Args, Body}) ->
ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args],
ArgPatterns = [{typed, As, Pat, T} || {arg, As, Pat, T} <- Args],
ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args],
ResultType = fresh_uvar(Attrs),
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} =
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
@ -1526,8 +1614,8 @@ unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
unify(Env, Named1, Named2, When) andalso
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
unify1(Env, {app_t, _, {id, _, F}, Args1}, {app_t, _, {id, _, F}, Args2}, When)
when length(Args1) == length(Args2) ->
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When)
when length(Args1) == length(Args2), Tag == id orelse Tag == qid ->
unify(Env, Args1, Args2, When);
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
when length(As) == length(Bs) ->
@ -1662,14 +1750,31 @@ type_error(Err) ->
create_type_errors() ->
ets_new(type_errors, [bag]).
destroy_and_report_type_errors() ->
destroy_and_report_type_errors(Env) ->
Errors = ets_tab2list(type_errors),
%% io:format("Type errors now: ~p\n", [Errors]),
PPErrors = [ pp_error(Err) || Err <- Errors ],
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
ets_delete(type_errors),
Errors /= [] andalso
error({type_errors, [lists:flatten(Err) || Err <- PPErrors]}).
%% Strip current namespace from error message for nicer printing.
unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) ->
qid(Ann, unqualify1(NS, Xs));
unqualify(#env{ namespace = NS }, {qcon, Ann, Xs}) ->
qcon(Ann, unqualify1(NS, Xs));
unqualify(Env, T) when is_tuple(T) ->
list_to_tuple(unqualify(Env, tuple_to_list(T)));
unqualify(Env, [H | T]) -> [unqualify(Env, H) | unqualify(Env, T)];
unqualify(_Env, X) -> X.
unqualify1(NS, Xs) ->
try lists:split(length(NS), Xs) of
{NS, Ys} -> Ys;
_ -> Xs
catch _:_ -> Xs
end.
pp_error({cannot_unify, A, B, When}) ->
io_lib:format("Cannot unify ~s\n"
" and ~s\n"
@ -1907,8 +2012,8 @@ pp({tuple_t, _, Cpts}) ->
["(", pp(Cpts), ")"];
pp({app_t, _, T, []}) ->
pp(T);
pp({app_t, _, {id, _, Name}, Args}) ->
[Name, "(", pp(Args), ")"];
pp({app_t, _, Type, Args}) ->
[pp(Type), "(", pp(Args), ")"];
pp({named_arg_t, _, Name, Type, Default}) ->
[pp(Name), " : ", pp(Type), " = ", pp(Default)];
pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->

View File

@ -58,7 +58,7 @@ contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode),
NewConstructors =
@ -70,7 +70,8 @@ contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
maps:from_list([ {QName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
_ -> #{}
end,
Icode1 = Icode#{ types := Types#{ Name => TypeDef },
{_, _, TName} = aeso_icode:qualify(Id, Icode),
Icode1 = Icode#{ types := Types#{ TName => TypeDef },
constructors := maps:merge(Constructors, NewConstructors) },
Icode2 = case Name of
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) };
@ -108,9 +109,11 @@ contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% just to parse a list of (mutually recursive) definitions.
contract_to_icode(Defs++Rest, Icode);
contract_to_icode([], Icode) -> Icode;
contract_to_icode(_Code, Icode) ->
%% TODO debug output for debug("Unhandled code ~p~n",[Code]),
Icode.
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
contract_to_icode(Code, Icode);
contract_to_icode([Decl | Code], Icode) ->
io:format("Unhandled declaration: ~p\n", [Decl]),
contract_to_icode(Code, Icode).
ast_id({id, _, Id}) -> Id;
ast_id({qid, _, Id}) -> Id.
@ -167,10 +170,10 @@ ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.spend'});
%% State
ast_body({id, _, "state"}, _Icode) -> prim_state;
ast_body(?id_app("put", [NewState], _, _), Icode) ->
ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state;
ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) ->
#prim_put{ state = ast_body(NewState, Icode) };
ast_body({id, _, "put"}, _Icode) ->
ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
gen_error({underapplied_primitive, put}); %% TODO: eta
%% Abort

View File

@ -161,8 +161,10 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) ->
get_arg_icode(Funs) ->
[Args] = [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ],
Args.
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args;
[] -> error({missing_call_function, Funs})
end.
get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType}

View File

@ -236,8 +236,10 @@ type({app_t, _, Type, Args}) ->
beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) ->
tuple_type(Args);
type({named_arg_t, _, Name, Type, Default}) ->
follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
typed(name(Name), Type);
type(R = {record_t, _}) -> typedef(R);
type(T = {id, _, _}) -> name(T);

View File

@ -75,7 +75,8 @@ compilable_contracts() ->
"builtin_bug",
"builtin_map_get_bug",
"nodeadcode",
"deadcode"
"deadcode",
"variant_types"
].
%% Contracts that should produce type errors
@ -181,4 +182,11 @@ failing_contracts() ->
, {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many string values (max 1)">>,
<<"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"
"when checking the record projection at line 12, column 40\n"
" r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]}
].

View File

@ -0,0 +1,13 @@
contract Remote =
type themap = map(int, string)
function foo : () => themap
contract Main =
type themap = map(string, int)
// Should fail
function foo(r : Remote) : themap = r.foo()