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/stdlib/src/erl_eval.erl | 20 ++++++++------------ lib/stdlib/test/erl_eval_SUITE.erl | 5 ++++- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl index 371573dc23..e86e10b170 100644 --- a/lib/stdlib/src/erl_eval.erl +++ b/lib/stdlib/src/erl_eval.erl @@ -246,18 +246,14 @@ expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> %% map expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs) -> {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, none), - case Map0 of - #{} -> - {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef), - Map1 = lists:foldl(fun ({map_assoc,K,V}, Mi) -> - maps:put(K, V, Mi); - ({map_exact,K,V}, Mi) -> - maps:update(K, V, Mi) - end, Map0, Vs), - ret_expr(Map1, merge_bindings(Bs2, Bs1), RBs); - _ -> - erlang:raise(error, {badarg,Map0}, stacktrace()) - end; + {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef), + _ = maps:put(k, v, Map0), %Validate map. + Map1 = lists:foldl(fun ({map_assoc,K,V}, Mi) -> + maps:put(K, V, Mi); + ({map_exact,K,V}, Mi) -> + maps:update(K, V, Mi) + end, Map0, Vs), + ret_expr(Map1, merge_bindings(Bs2, Bs1), RBs); expr({map,_,Es}, Bs0, Lf, Ef, RBs) -> {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef), ret_expr(lists:foldl(fun diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl index 3427f431c5..a750c5cace 100644 --- a/lib/stdlib/test/erl_eval_SUITE.erl +++ b/lib/stdlib/test/erl_eval_SUITE.erl @@ -1482,8 +1482,11 @@ eep43(Config) when is_list(Config) -> " #{ K1 := 1, K2 := 2, K3 := 3, {2,2} := 4} = Map " "end.", #{ 1 => 1, <<42:301>> => 2, {3,<<42:301>>} => 3, {2,2} => 4}), - error_check("[camembert]#{}.", {badarg,[camembert]}), + error_check("[camembert]#{}.", {badmap,[camembert]}), + error_check("[camembert]#{nonexisting:=v}.", {badmap,[camembert]}), error_check("#{} = 1.", {badmatch,1}), + error_check("[]#{a=>error(bad)}.", bad), + error_check("(#{})#{nonexisting:=value}.", {badkey,nonexisting}), ok. %% Check the string in different contexts: as is; in fun; from compiled code. -- cgit v1.2.3 From 5a19f97ebb036f7e9f6e2c735d9f52662b20a6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 10 Apr 2015 12:18:59 +0200 Subject: Document the new {badmap,Term} and {badkey,Key} exceptions --- lib/stdlib/doc/src/maps.xml | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml index f766c843be..59c26d9896 100644 --- a/lib/stdlib/doc/src/maps.xml +++ b/lib/stdlib/doc/src/maps.xml @@ -40,6 +40,9 @@ Returns a tuple {ok, Value} where Value is the value associated with Key, or error if no value is associated with Key in Map.

+

+ The call will fail with a {badmap,Map} exception if Map is not a map. +

Example:

> Map = #{"hi" => 42}, @@ -95,8 +98,10 @@

Returns the value Value associated with Key if Map contains Key. - If no value is associated with Key then the call will - fail with an exception. +

+

+ The call will fail with a {badmap,Map} exception if Map is not a map, + or with a {badkey,Key} exception if no value is associated with Key.

Example:

@@ -116,6 +121,10 @@ Map contains Key. If no value is associated with Key then returns Default.

+

+ The call will fail with a {badmap,Map} exception if Map is not a map. + +

Example:

> Map = #{ key1 => val1, key2 => val2 }. @@ -134,7 +143,9 @@ val1

Returns true if map Map contains Key and returns false if it does not contain the Key. - The function will fail with an exception if Map is not a Map. +

+

+ The call will fail with a {badmap,Map} exception if Map is not a map.

Example:

@@ -154,6 +165,9 @@ false

Returns a complete list of keys, in arbitrary order, which resides within Map.

+

+ The call will fail with a {badmap,Map} exception if Map is not a map. +

Example:

> Map = #{42 => value_three,1337 => "value two","a" => 1}, @@ -189,6 +203,10 @@ false Merges two maps into a single map Map3. If two keys exists in both maps the value in Map1 will be superseded by the value in Map2.

+

+ The call will fail with a {badmap,Map} exception if Map1 or + Map2 is not a map. +

Example:

> Map1 = #{a => "value_one", b => "value_two"}, @@ -222,6 +240,10 @@ false replaced by value Value. The function returns a new map Map2 containing the new association and the old associations in Map1.

+

+ The call will fail with a {badmap,Map} exception if Map1 is not a map. +

+

Example:

> Map = #{"a" => 1}. @@ -241,6 +263,9 @@ false The function removes the Key, if it exists, and its associated value from Map1 and returns a new map Map2 without key Key.

+

+ The call will fail with a {badmap,Map} exception if Map1 is not a map. +

Example:

> Map = #{"a" => 1}. @@ -276,6 +301,9 @@ false The fuction returns a list of pairs representing the key-value associations of Map, where the pairs, [{K1,V1}, ..., {Kn,Vn}], are returned in arbitrary order.

+

+ The call will fail with a {badmap,Map} exception if Map is not a map. +

Example:

> Map = #{42 => value_three,1337 => "value two","a" => 1}, @@ -291,8 +319,11 @@ false

If Key exists in Map1 the old associated value is replaced by value Value. The function returns a new map Map2 containing - the new associated value. If Key does not exist in Map1 an exception is - generated. + the new associated value. +

+

+ The call will fail with a {badmap,Map} exception if Map1 is not a map, + or with a {badkey,Key} exception if no value is associated with Key.

Example:

@@ -310,6 +341,9 @@ false

Returns a complete list of values, in arbitrary order, contained in map M.

+

+ The call will fail with a {badmap,Map} exception if Map is not a map. +

Example:

> Map = #{42 => value_three,1337 => "value two","a" => 1}, -- cgit v1.2.3