aboutsummaryrefslogtreecommitdiffstats
path: root/erts
diff options
context:
space:
mode:
authorMichał Muskała <[email protected]>2018-04-27 12:40:07 +0200
committerMichał Muskała <[email protected]>2018-04-29 17:03:43 +0200
commit5d0874a8f9fd617308d9024783db1e4e24268184 (patch)
treeac61f2f0f8d7f7483af9e17df44b49aeb049e695 /erts
parentd423a7af502227afcdcf7d2a1efecded85ea95fb (diff)
downloadotp-5d0874a8f9fd617308d9024783db1e4e24268184.tar.gz
otp-5d0874a8f9fd617308d9024783db1e4e24268184.tar.bz2
otp-5d0874a8f9fd617308d9024783db1e4e24268184.zip
Introduce is_map_key/2 guard BIF
This complements the `map_get/2` guard BIF introduced in #1784. Rationale. `map_get/2` allows accessing map fields in guards, but it might be problematic in more complex guard expressions, for example: foo(X) when map_get(a, X) =:= 1 or is_list(X) -> ... The `is_list/1` part of the guard could never succeed since the `map_get/2` guard would fail the whole guard expression. In this situation, this could be solved by using `;` instead of `or` to separate the guards, but it is not possible in every case. To solve this situation, this PR proposes a `is_map_key/2` guard that allows to check if a map has key inside a guard before trying to access that key. When combined with `is_map/1` this allows to construct a purely boolean guard expression testing a value of a key in a map. Implementation. Given the use case motivating the introduction of this function, the PR contains compiler optimisations that produce optimial code for the following guard expression: foo(X) when is_map(X) and is_map_key(a, X) and map_get(a, X) =:= 1 -> ok; foo(_) -> error. Given all three tests share the failure label, the `is_map_key/2` and `is_map/2` tests are optimised away. As with `map_get/2` the `is_map_key/2` BIF is allowed in match specs.
Diffstat (limited to 'erts')
-rw-r--r--erts/doc/src/erlang.xml20
-rw-r--r--erts/doc/src/match_spec.xml23
-rw-r--r--erts/emulator/beam/bif.tab1
-rw-r--r--erts/emulator/beam/erl_db_util.c6
-rw-r--r--erts/emulator/beam/erl_map.c7
-rw-r--r--erts/emulator/test/map_SUITE.erl43
-rw-r--r--erts/emulator/test/match_spec_SUITE.erl7
-rw-r--r--erts/preloaded/src/erlang.erl9
8 files changed, 102 insertions, 14 deletions
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index f561413fab..3154fdaf8c 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -2432,6 +2432,26 @@ os_prompt%</pre>
</func>
<func>
+ <name name="is_map_key" arity="2"/>
+ <fsummary></fsummary>
+ <desc>
+ <p>Returns <c>true</c> if map <c><anno>Map</anno></c> contains
+ <c><anno>Key</anno></c> and returns <c>false</c> if it does not
+ contain the <c><anno>Key</anno></c>.</p>
+ <p>The call fails with a <c>{badmap,Map}</c> exception if
+ <c><anno>Map</anno></c> is not a map.</p>
+ <p><em>Example:</em></p>
+ <code type="none">
+> Map = #{"42" => value}.
+#{"42" => value}
+> is_map_key("42",Map).
+true
+> is_map_key(value,Map).
+false</code>
+ </desc>
+ </func>
+
+ <func>
<name name="is_number" arity="1"/>
<fsummary>Check whether a term is a number.</fsummary>
<desc>
diff --git a/erts/doc/src/match_spec.xml b/erts/doc/src/match_spec.xml
index 888366b239..46a3daebe8 100644
--- a/erts/doc/src/match_spec.xml
+++ b/erts/doc/src/match_spec.xml
@@ -86,12 +86,12 @@
<c><![CDATA[is_list]]></c> | <c><![CDATA[is_number]]></c> |
<c><![CDATA[is_pid]]></c> | <c><![CDATA[is_port]]></c> |
<c><![CDATA[is_reference]]></c> | <c><![CDATA[is_tuple]]></c> |
- <c><![CDATA[is_map]]></c> | <c><![CDATA[is_binary]]></c> |
- <c><![CDATA[is_function]]></c> | <c><![CDATA[is_record]]></c> |
- <c><![CDATA[is_seq_trace]]></c> | <c><![CDATA['and']]></c> |
- <c><![CDATA['or']]></c> | <c><![CDATA['not']]></c> |
- <c><![CDATA['xor']]></c> | <c><![CDATA['andalso']]></c> |
- <c><![CDATA['orelse']]></c>
+ <c><![CDATA[is_map]]></c> | <c><![CDATA[is_map_key]]></c> |
+ <c><![CDATA[is_binary]]></c> | <c><![CDATA[is_function]]></c> |
+ <c><![CDATA[is_record]]></c> | <c><![CDATA[is_seq_trace]]></c> |
+ <c><![CDATA['and']]></c> | <c><![CDATA['or']]></c> |
+ <c><![CDATA['not']]></c> | <c><![CDATA['xor']]></c> |
+ <c><![CDATA['andalso']]></c> | <c><![CDATA['orelse']]></c>
</item>
<item>ConditionExpression ::= ExprMatchVariable | { GuardFunction } |
{ GuardFunction, ConditionExpression, ... } | TermConstruct
@@ -168,11 +168,12 @@
<c><![CDATA[is_list]]></c> | <c><![CDATA[is_number]]></c> |
<c><![CDATA[is_pid]]></c> | <c><![CDATA[is_port]]></c> |
<c><![CDATA[is_reference]]></c> | <c><![CDATA[is_tuple]]></c> |
- <c><![CDATA[is_map]]></c> | <c><![CDATA[is_binary]]></c> |
- <c><![CDATA[is_function]]></c> | <c><![CDATA[is_record]]></c> |
- <c><![CDATA['and']]></c> | <c><![CDATA['or']]></c> |
- <c><![CDATA['not']]></c> | <c><![CDATA['xor']]></c> |
- <c><![CDATA['andalso']]></c> | <c><![CDATA['orelse']]></c>
+ <c><![CDATA[is_map]]></c> | <c><![CDATA[map_is_key]]></c> |
+ <c><![CDATA[is_binary]]></c> | <c><![CDATA[is_function]]></c> |
+ <c><![CDATA[is_record]]></c> | <c><![CDATA['and']]></c> |
+ <c><![CDATA['or']]></c> | <c><![CDATA['not']]></c> |
+ <c><![CDATA['xor']]></c> | <c><![CDATA['andalso']]></c> |
+ <c><![CDATA['orelse']]></c>
</item>
<item>ConditionExpression ::= ExprMatchVariable | { GuardFunction } |
{ GuardFunction, ConditionExpression, ... } | TermConstruct
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 276bef2bbb..33738dc20b 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -696,3 +696,4 @@ bif ets:whereis/1
bif erts_internal:gather_alloc_histograms/1
bif erts_internal:gather_carrier_info/1
ubif erlang:map_get/2
+ubif erlang:is_map_key/2
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index 6354abfd1f..ef22cda1f0 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -644,6 +644,12 @@ static DMCGuardBif guard_tab[] =
DBIF_ALL
},
{
+ am_is_map_key,
+ &is_map_key_2,
+ 2,
+ DBIF_ALL
+ },
+ {
am_bit_size,
&bit_size_1,
1,
diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index f577b017c3..3d565b1bb8 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -43,6 +43,7 @@
*
* DONE:
* - erlang:is_map/1
+ * - erlang:is_map_key/2
* - erlang:map_size/1
* - erlang:map_get/2
*
@@ -919,7 +920,7 @@ static int hxnodecmp(hxnode_t *a, hxnode_t *b) {
return -1;
}
-/* maps:is_key/2 */
+/* maps:is_key/2 and erlang:is_map_key/2 */
BIF_RETTYPE maps_is_key_2(BIF_ALIST_2) {
if (is_map(BIF_ARG_2)) {
@@ -929,6 +930,10 @@ BIF_RETTYPE maps_is_key_2(BIF_ALIST_2) {
BIF_ERROR(BIF_P, BADMAP);
}
+BIF_RETTYPE is_map_key_2(BIF_ALIST_2) {
+ BIF_RET(maps_is_key_2(BIF_CALL_ARGS));
+}
+
/* maps:keys/1 */
BIF_RETTYPE maps_keys_1(BIF_ALIST_1) {
diff --git a/erts/emulator/test/map_SUITE.erl b/erts/emulator/test/map_SUITE.erl
index 43807b4388..f93c637650 100644
--- a/erts/emulator/test/map_SUITE.erl
+++ b/erts/emulator/test/map_SUITE.erl
@@ -38,6 +38,7 @@
t_map_size/1,
t_map_get/1,
t_is_map/1,
+ t_is_map_key/1,
%% Specific Map BIFs
t_bif_map_get/1,
@@ -718,7 +719,47 @@ t_map_get(Config) when is_list(Config) ->
true = if map_get(a, M2) =:= 1 -> true; true -> false end,
false = if map_get(x, M2) =:= 1 -> true; true -> false end,
do_badmap(fun
- (T) when map_get(T, x) =:= 1 -> ok;
+ (T) when map_get(x, T) =:= 1 -> ok;
+ (T) -> false = is_map(T)
+ end),
+ ok.
+
+t_is_map_key(Config) when is_list(Config) ->
+ %% small map
+ true = is_map_key(a, id(#{a=>1})),
+ true = is_map_key(b, id(#{a=>1, b=>2})),
+ true = is_map_key("hello", id(#{a=>1, "hello"=>"hi"})),
+ true = is_map_key({1,1.0}, id(#{a=>a, {1,1.0}=>"tuple hi"})),
+
+ M0 = id(#{ k1=>"v1", <<"k2">> => <<"v3">> }),
+ true = is_map_key(<<"k2">>, M0#{<<"k2">> => "v4"}),
+
+ %% large map
+ M1 = maps:from_list([{I,I}||I<-lists:seq(1,100)] ++
+ [{a,1},{b,2},{"hello","hi"},{{1,1.0},"tuple hi"},
+ {k1,"v1"},{<<"k2">>,"v3"}]),
+ true = is_map_key(a, M1),
+ true = is_map_key(b, M1),
+ true = is_map_key("hello", M1),
+ true = is_map_key({1,1.0}, M1),
+ true = is_map_key(<<"k2">>, M1),
+
+ %% error cases
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{erlang,is_map_key,_,_}|_]}} =
+ (catch is_map_key(a, T))
+ end),
+
+ false = is_map_key({1,1}, id(#{{1,1.0}=>"tuple"})),
+ false = is_map_key(a, id(#{})),
+ false = is_map_key(a, id(#{b=>1, c=>2})),
+
+ %% in guards
+ M2 = id(#{a=>1}),
+ true = if is_map_key(a, M2) -> true; true -> false end,
+ false = if is_map_key(x, M2) -> true; true -> false end,
+ do_badmap(fun
+ (T) when is_map_key(T, x) =:= 1 -> ok;
(T) -> false = is_map(T)
end),
ok.
diff --git a/erts/emulator/test/match_spec_SUITE.erl b/erts/emulator/test/match_spec_SUITE.erl
index c1bc01f01e..4415d8d1b9 100644
--- a/erts/emulator/test/match_spec_SUITE.erl
+++ b/erts/emulator/test/match_spec_SUITE.erl
@@ -898,6 +898,13 @@ maps(Config) when is_list(Config) ->
{ok,false,[],[]} = erlang:match_spec_test(not_a_map, [{'$1',[{map_get,b,'$1'}],['$_']}], table),
{ok,true,[],[]} = erlang:match_spec_test(#{a => true}, [{'$1',[{map_get,a,'$1'}],[true]}], table),
+ {ok,true,[],[]} = erlang:match_spec_test(#{a => 1}, [{'$1',[],[{is_map_key,a,'$1'}]}], table),
+ {ok,false,[],[]} = erlang:match_spec_test(#{a => 1}, [{'$1',[],[{is_map_key,b,'$1'}]}], table),
+ {ok,'EXIT',[],[]} = erlang:match_spec_test(not_a_map, [{'$1',[],[{is_map_key,a,'$1'}]}], table),
+ {ok,false,[],[]} = erlang:match_spec_test(#{a => 1}, [{'$1',[{is_map_key,b,'$1'}],['$_']}], table),
+ {ok,false,[],[]} = erlang:match_spec_test(not_a_map, [{'$1',[{is_map_key,b,'$1'}],['$_']}], table),
+ {ok,true,[],[]} = erlang:match_spec_test(#{a => true}, [{'$1',[{is_map_key,a,'$1'}],[true]}], table),
+
%% large maps
Ls0 = [{I,<<I:32>>}||I <- lists:seq(1,415)],
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 53e90a4f2d..5fcad25c6d 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -135,7 +135,7 @@
-export([insert_element/3]).
-export([integer_to_binary/1, integer_to_list/1]).
-export([iolist_size/1, iolist_to_binary/1, iolist_to_iovec/1]).
--export([is_alive/0, is_builtin/3, is_process_alive/1, length/1, link/1]).
+-export([is_alive/0, is_builtin/3, is_map_key/2, is_process_alive/1, length/1, link/1]).
-export([list_to_atom/1, list_to_binary/1]).
-export([list_to_bitstring/1, list_to_existing_atom/1, list_to_float/1]).
-export([list_to_integer/1, list_to_integer/2]).
@@ -1121,6 +1121,13 @@ is_alive() ->
is_builtin(_Module, _Function, _Arity) ->
erlang:nif_error(undefined).
+%% Shadowed by erl_bif_types: erlang:is_map_key/2
+-spec is_map_key(Key, Map) -> boolean() when
+ Key :: term(),
+ Map :: map().
+is_map_key(_,_) ->
+ erlang:nif_error(undef).
+
%% is_process_alive/1
-spec is_process_alive(Pid) -> boolean() when
Pid :: pid().