Skip to content

Commit e602076

Browse files
committed
Add full support for maps
1 parent 3bbd252 commit e602076

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

src/proper_gen.erl

+11-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
binary_rev/1, binary_len_gen/1, bitstring_gen/1, bitstring_rev/1,
4343
bitstring_len_gen/1, list_gen/2, distlist_gen/3, vector_gen/2,
4444
union_gen/1, weighted_union_gen/1, tuple_gen/1, loose_tuple_gen/2,
45-
loose_tuple_rev/2, exactly_gen/1, fixed_list_gen/1, function_gen/2,
46-
any_gen/1, native_type_gen/2, safe_weighted_union_gen/1,
45+
loose_tuple_rev/2, exactly_gen/1, fixed_list_gen/1, fixed_map_gen/1,
46+
function_gen/2, any_gen/1, native_type_gen/2, safe_weighted_union_gen/1,
4747
safe_union_gen/1]).
4848

4949
%% Public API types
@@ -576,6 +576,15 @@ fixed_list_gen({ProperHead,ImproperTail}) ->
576576
fixed_list_gen(ProperFields) ->
577577
[generate(F) || F <- ProperFields].
578578

579+
%% @private
580+
-spec fixed_map_gen(map()) -> imm_instance().
581+
fixed_map_gen(Map) when is_map(Map) ->
582+
maps:from_list([
583+
{generate(KeyOrType), generate(ValueOrType)}
584+
||
585+
{KeyOrType, ValueOrType} <- maps:to_list(Map)
586+
]).
587+
579588
%% @private
580589
-spec function_gen(arity(), proper_types:type()) -> function().
581590
function_gen(Arity, RetType) ->

src/proper_types.erl

+62-3
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@
141141

142142
-export([integer/2, float/2, atom/0, binary/0, binary/1, bitstring/0,
143143
bitstring/1, list/1, vector/2, union/1, weighted_union/1, tuple/1,
144-
loose_tuple/1, exactly/1, fixed_list/1, function/2, map/0, map/2,
145-
any/0, shrink_list/1, safe_union/1, safe_weighted_union/1]).
144+
loose_tuple/1, exactly/1, fixed_list/1, fixed_map/1, function/2, map/0,
145+
map/2, any/0, shrink_list/1, safe_union/1, safe_weighted_union/1]).
146146
-export([integer/0, non_neg_integer/0, pos_integer/0, neg_integer/0, range/2,
147147
float/0, non_neg_float/0, number/0, boolean/0, byte/0, char/0, nil/0,
148148
list/0, tuple/0, string/0, wunion/1, term/0, timeout/0, arity/0]).
@@ -258,7 +258,7 @@
258258
| {'shrinkers', [proper_shrink:shrinker()]}
259259
| {'noshrink', boolean()}
260260
| {'internal_type', raw_type()}
261-
| {'internal_types', tuple() | maybe_improper_list(type(),type() | [])}
261+
| {'internal_types', tuple() | map() | maybe_improper_list(type(),type() | [])}
262262
%% The items returned by 'remove' must be of this type.
263263
| {'get_length', fun((proper_gen:imm_instance()) -> length())}
264264
%% If this is a container type, this should return the number of elements
@@ -312,6 +312,8 @@ cook_outer(RawType) when is_tuple(RawType) ->
312312
tuple(tuple_to_list(RawType));
313313
cook_outer(RawType) when is_list(RawType) ->
314314
fixed_list(RawType); %% CAUTION: this must handle improper lists
315+
cook_outer(RawType) when is_map(RawType) ->
316+
fixed_map(RawType);
315317
cook_outer(RawType) -> %% default case (integers, floats, atoms, binaries, ...)
316318
exactly(RawType).
317319

@@ -1124,6 +1126,63 @@ map() ->
11241126
map(K, V) ->
11251127
?LET(L, list({K, V}), maps:from_list(L)).
11261128

1129+
%% @doc A map whose keys and values are defined by the given `Map'.
1130+
%% Also written simply as a {@link maps. map}.
1131+
-spec fixed_map(#{Key::raw_type() => Value::raw_type()}) -> proper_types:type().
1132+
% fixed_map(Map) when is_map(Map) ->
1133+
% Pairs = maps:to_list(Map),
1134+
% ?LET(L, fixed_list(Pairs), maps:from_list(L)).
1135+
1136+
fixed_map(Map) when is_map(Map) ->
1137+
?CONTAINER([
1138+
{generator, {typed, fun map_gen/1}},
1139+
{is_instance, {typed, fun map_is_instance/2}},
1140+
{internal_types, Map},
1141+
{get_length, fun maps:size/1},
1142+
{join, fun maps:merge/2},
1143+
{get_indices, fun maps:keys/1},
1144+
{remove, fun maps:remove/2},
1145+
{retrieve, fun maps:get/2},
1146+
{update, fun maps:update/3}
1147+
]).
1148+
1149+
map_gen(Type) ->
1150+
Map = get_prop(internal_types, Type),
1151+
proper_gen:fixed_map_gen(Map).
1152+
1153+
map_is_instance(Type, X) when is_map(X) ->
1154+
Map = get_prop(internal_types, Type),
1155+
map_all(
1156+
fun (Key, ValueType) when is_map_key(Key, X) ->
1157+
is_instance(maps:get(Key, X), ValueType);
1158+
(KeyOrType, ValueType) ->
1159+
case is_raw_type(KeyOrType) of
1160+
true ->
1161+
map_all(fun(Key, Value) ->
1162+
case is_instance(Key, KeyOrType) of
1163+
true -> is_instance(Value, ValueType);
1164+
false -> true %% Ignore other keys
1165+
end
1166+
end, X);
1167+
false ->
1168+
%% The key not a type and not in `X'
1169+
false
1170+
end
1171+
end,
1172+
Map
1173+
);
1174+
map_is_instance(_Type, _X) ->
1175+
false.
1176+
1177+
map_all(Fun, Map) when is_function(Fun, 2) andalso is_map(Map) ->
1178+
map_all_internal(Fun, maps:next(maps:iterator(Map)), true).
1179+
1180+
map_all_internal(Fun, _, false) when is_function(Fun, 2) ->
1181+
false;
1182+
map_all_internal(Fun, none, Result) when is_function(Fun, 2) andalso is_boolean(Result) ->
1183+
Result;
1184+
map_all_internal(Fun, {Key, Value, NextIterator}, true) when is_function(Fun, 2) ->
1185+
map_all_internal(Fun, NextIterator, Fun(Key, Value)).
11271186

11281187
%% @doc All Erlang terms (that PropEr can produce). For reasons of efficiency,
11291188
%% functions are never produced as instances of this type.<br />

0 commit comments

Comments
 (0)