From 5bb8262bd4ae8dbcc7438e80de5cedac7ccee1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 31 Mar 2015 08:47:57 +0200 Subject: Raise more descriptive error messages for failed map operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to EEP-43 for maps, a 'badmap' exception should be generated when an attempt is made to update non-map term such as: <<>>#{a=>42} That was not implemented in the OTP 17. José Valim suggested that we should take the opportunity to improve the errors coming from map operations: http://erlang.org/pipermail/erlang-questions/2015-February/083588.html This commit implement better errors from map operations similar to his suggestion. When a map update operation (Map#{...}) or a BIF that expects a map is given a non-map term, the exception will be: {badmap,Term} This kind of exception is similar to the {badfun,Term} exception from operations that expect a fun. When a map operation requires a key that is not present in a map, the following exception will be raised: {badkey,Key} José Valim suggested that the exception should be {badkey,Key,Map}. We decided not to do that because the map could potentially be huge and cause problems if the error propagated through links to other processes. For BIFs, it could be argued that the exceptions could be simply 'badmap' and 'badkey', because the bad map and bad key can be found in the argument list for the BIF in the stack backtrace. However, for the map update operation (Map#{...}), the bad map or bad key will not be included in the stack backtrace, so that information must be included in the exception reason itself. For consistency, the BIFs should raise the same exceptions as update operation. If more than one key is missing, it is undefined which of keys that will be reported in the {badkey,Key} exception. --- lib/hipe/icode/hipe_beam_to_icode.erl | 15 +++++++++------ lib/hipe/test/maps_SUITE_data/maps_map_size.erl | 6 +++--- lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl | 2 +- lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl | 4 ++-- lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl | 2 +- lib/hipe/test/maps_SUITE_data/maps_update_exact.erl | 10 +++++----- .../test/maps_SUITE_data/maps_update_map_expressions.erl | 6 +++--- 7 files changed, 24 insertions(+), 21 deletions(-) (limited to 'lib/hipe') diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl index 4691662f9f..3e099fcc25 100644 --- a/lib/hipe/icode/hipe_beam_to_icode.erl +++ b/lib/hipe/icode/hipe_beam_to_icode.erl @@ -1584,11 +1584,7 @@ gen_put_map_instrs(exists, Op, TempMapVar, Dst, FailLbl, Pairs, Env) -> end, {[IsMapCode, TrueLabel, PutInstructions, ReturnLbl], Env1}; gen_put_map_instrs(new, Op, TempMapVar, Dst, new, Pairs, Env) -> - TrueLabel = mk_label(new), FailLbl = mk_label(new), - IsMapCode = hipe_icode:mk_type([TempMapVar], map, - hipe_icode:label_name(TrueLabel), - hipe_icode:label_name(FailLbl)), DstMapVar = mk_var(Dst), {ReturnLbl, PutInstructions, Env1} = case Op of @@ -1596,10 +1592,10 @@ gen_put_map_instrs(new, Op, TempMapVar, Dst, new, Pairs, Env) -> trans_put_map_assoc(TempMapVar, DstMapVar, Pairs, Env, []); exact -> trans_put_map_exact(TempMapVar, DstMapVar, - hipe_icode:label_name(FailLbl), Pairs, Env, []) + none, Pairs, Env, []) end, Fail = hipe_icode:mk_fail([hipe_icode:mk_const(badarg)], error), - {[IsMapCode, TrueLabel, PutInstructions, FailLbl, Fail, ReturnLbl], Env1}. + {[PutInstructions, FailLbl, Fail, ReturnLbl], Env1}. %%----------------------------------------------------------------------- %% This function generates the instructions needed to insert several @@ -1629,6 +1625,13 @@ trans_put_map_exact(MapVar, DestMapVar, _FLbl, [], Env, Acc) -> ReturnLbl = mk_label(new), GotoReturn = hipe_icode:mk_goto(hipe_icode:label_name(ReturnLbl)), {ReturnLbl, lists:reverse([GotoReturn, MoveToReturnVar | Acc]), Env}; +trans_put_map_exact(MapVar, DestMapVar, none, [Key, Value | Rest], Env, Acc) -> + {MoveKey, KeyVar, Env1} = mk_move_and_var(Key, Env), + {MoveVal, ValVar, Env2} = mk_move_and_var(Value, Env1), + BifCallPut = hipe_icode:mk_call([MapVar], maps, update, + [KeyVar, ValVar, MapVar], remote), + Acc1 = [BifCallPut, MoveVal, MoveKey | Acc], + trans_put_map_exact(MapVar, DestMapVar, none, Rest, Env2, Acc1); trans_put_map_exact(MapVar, DestMapVar, FLbl, [Key, Value | Rest], Env, Acc) -> SuccLbl = mk_label(new), {MoveKey, KeyVar, Env1} = mk_move_and_var(Key, Env), diff --git a/lib/hipe/test/maps_SUITE_data/maps_map_size.erl b/lib/hipe/test/maps_SUITE_data/maps_map_size.erl index 25c8e5d4c7..3cd2d90dfb 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_map_size.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_map_size.erl @@ -17,9 +17,9 @@ test() -> false = map_is_size(M#{ "c" => 2}, 2), %% Error cases. - {'EXIT',{badarg,_}} = (catch map_size([])), - {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), - {'EXIT',{badarg,_}} = (catch map_size(1)), + {'EXIT',{{badmap,[]},_}} = (catch map_size([])), + {'EXIT',{{badmap,<<1,2,3>>},_}} = (catch map_size(<<1,2,3>>)), + {'EXIT',{{badmap,1},_}} = (catch map_size(1)), ok. map_is_size(M,N) when map_size(M) =:= N -> true; diff --git a/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl b/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl index 72ac9ce078..2fe4f204d1 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl @@ -8,7 +8,7 @@ test() -> true = assoc_guard(#{}), false = assoc_guard(not_a_map), #{a := true} = assoc_update(#{}), - {'EXIT', {badarg, [{?MODULE, assoc_update, 1, _}|_]}} + {'EXIT', {{badmap, not_a_map}, [{?MODULE, assoc_update, 1, _}|_]}} = (catch assoc_update(not_a_map)), ok = assoc_guard_clause(#{}), {'EXIT', {function_clause, [{?MODULE, assoc_guard_clause, _, _}|_]}} diff --git a/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl b/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl index 1cfcd80180..3c85289a36 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl @@ -9,9 +9,9 @@ test() -> false = exact_guard(not_a_map), true = exact_guard(#{a => false}), #{a := true} = exact_update(#{a => false}), - {'EXIT', {badarg, [{?MODULE, exact_update, 1, _}|_]}} + {'EXIT', {{badmap, not_a_map}, [{?MODULE, exact_update, 1, _}|_]}} = (catch exact_update(not_a_map)), - {'EXIT', {badarg, [{?MODULE, exact_update, 1, _}|_]}} + {'EXIT', {{badkey, a}, [{?MODULE, exact_update, 1, _}|_]}} = (catch exact_update(#{})), ok = exact_guard_clause(#{a => yes}), {'EXIT', {function_clause, [{?MODULE, exact_guard_clause, _, _}|_]}} diff --git a/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl b/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl index cc7c1353de..99228a1927 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl @@ -14,7 +14,7 @@ test() -> %% Errors cases. BadMap = id(badmap), - {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), + {'EXIT',{{badmap,badmap},_}} = (catch BadMap#{nonexisting=>val}), ok. diff --git a/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl b/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl index 6e5acb3283..1c38820a7c 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl @@ -21,11 +21,11 @@ test() -> 1.0 => new_val4 }, %% Errors cases. - {'EXIT',{badarg,_}} = (catch ((id(nil))#{ a := b })), - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badmap,nil},_}} = (catch ((id(nil))#{ a := b })), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,_},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), ok. %% Use this function to avoid compile-time evaluation of an expression. diff --git a/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl b/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl index 181e3f18f7..213fc33d97 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl @@ -23,9 +23,9 @@ test() -> #{ "a" := b } = F(), - %% Error cases, FIXME: should be 'badmap'? - {'EXIT',{badarg,_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), - {'EXIT',{badarg,_}} = (catch (id([]))#{ a := 42, b => 2 }), + %% Error cases. + {'EXIT',{{badmap,<<>>},_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), + {'EXIT',{{badmap,[]},_}} = (catch (id([]))#{ a := 42, b => 2 }), ok. %% Use this function to avoid compile-time evaluation of an expression. -- cgit v1.2.3