From 0533ab27e1f9751538ce0764818bb50e5387a8db Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 3 Sep 2019 12:04:22 +0200 Subject: [PATCH] Check that there are no maps in map keys already in type checker --- src/aeso_ast_infer_types.erl | 17 ++++++++++++++++- src/aeso_ast_to_icode.erl | 22 +++++----------------- test/aeso_compiler_tests.erl | 9 +++++++++ test/contracts/map_as_map_key.aes | 6 ++++++ 4 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 test/contracts/map_as_map_key.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 88fdc12..ba8abf5 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1708,7 +1708,7 @@ solve_known_record_types(Env, Constraints) -> C end; _ -> - type_error({not_a_record_type, RecId, When}), + type_error({not_a_record_type, RecType, When}), not_solved end end @@ -1823,6 +1823,10 @@ unfold_types(_Env, X, _Options) -> unfold_types_in_type(Env, T) -> unfold_types_in_type(Env, T, []). +unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]}, Options) -> + Args1 = [KeyType, _] = unfold_types_in_type(Env, Args, Options), + [ type_error({map_in_map_key, KeyType0}) || has_maps(KeyType) ], + {app_t, Ann, Id, Args1}; unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) -> UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false), @@ -1870,6 +1874,13 @@ unfold_types_in_type(Env, [H|T], Options) -> unfold_types_in_type(_Env, X, _Options) -> X. +has_maps({app_t, _, {id, _, "map"}, _}) -> + true; +has_maps(L) when is_list(L) -> + lists:any(fun has_maps/1, L); +has_maps(T) when is_tuple(T) -> + has_maps(tuple_to_list(T)); +has_maps(_) -> false. subst_tvars(Env, Type) -> subst_tvars1([{V, T} || {{tvar, _, V}, T} <- Env], Type). @@ -2282,6 +2293,10 @@ mk_error({new_tuple_syntax, Ann, Ts}) -> Msg = io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n", [pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]), mk_t_err(pos(Ann), Msg); +mk_error({map_in_map_key, KeyType}) -> + Msg = io_lib:format("Invalid key type\n~s\n", [pp_type(" ", KeyType)]), + Cxt = "Map keys cannot contain other maps.\n", + mk_t_err(pos(KeyType), Msg, Cxt); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 9918a3b..aadf843 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -718,15 +718,11 @@ eta_expand(Id = {_, Ann0, _}, {fun_t, _, _, ArgsT, _}, Icode) -> check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map(Ann, MapType, Icode). -check_monomorphic_map(Ann, Type = ?map_t(KeyType, ValType), Icode) -> - case is_monomorphic(KeyType) of - true -> - case has_maps(ast_type(KeyType, Icode)) of - false -> {KeyType, ValType}; - true -> gen_error({cant_use_map_as_map_keys, Ann, Type}) - end; - false -> gen_error({cant_compile_map_with_polymorphic_keys, Ann, Type}) - end. +check_monomorphic_map(Ann, ?map_t(KeyType, ValType), _Icode) -> + Err = fun(Why) -> gen_error({invalid_map_key_type, Why, Ann, KeyType}) end, + [ Err(polymorphic) || not is_monomorphic(KeyType) ], + [ Err(function) || not is_first_order_type(KeyType) ], + {KeyType, ValType}. map_empty(KeyType, ValType, Icode) -> prim_call(?PRIM_CALL_MAP_EMPTY, #integer{value = 0}, @@ -928,14 +924,6 @@ ast_fun_to_icode(Name, Attrs, Args, Body, TypeRep, #{functions := Funs} = Icode) NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs], aeso_icode:set_functions(NewFuns, Icode). -has_maps({map, _, _}) -> true; -has_maps(word) -> false; -has_maps(string) -> false; -has_maps(typerep) -> false; -has_maps({list, T}) -> has_maps(T); -has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); -has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). - %% A function is private if not an 'entrypoint', or if it's not defined in the %% main contract name space. (NOTE: changes when we introduce inheritance). is_private(Ann, #{ contract_name := MainContract } = Icode) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 68efabf..94d9a0b 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -468,6 +468,15 @@ failing_contracts() -> [<> ]} + , {"map_as_map_key", + [<>, + <>]} ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/map_as_map_key.aes b/test/contracts/map_as_map_key.aes new file mode 100644 index 0000000..8b2caf4 --- /dev/null +++ b/test/contracts/map_as_map_key.aes @@ -0,0 +1,6 @@ +contract MapAsMapKey = + type t('key) = map('key, int) + type lm = list(map(int, int)) + + entrypoint foo(m) : t(map(int, int)) = {[m] = 0} + entrypoint bar(m) : t(lm) = Map.delete(m, {})