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