|
141 | 141 |
|
142 | 142 | -export([integer/2, float/2, atom/0, binary/0, binary/1, bitstring/0,
|
143 | 143 | 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]). |
146 | 146 | -export([integer/0, non_neg_integer/0, pos_integer/0, neg_integer/0, range/2,
|
147 | 147 | float/0, non_neg_float/0, number/0, boolean/0, byte/0, char/0, nil/0,
|
148 | 148 | list/0, tuple/0, string/0, wunion/1, term/0, timeout/0, arity/0]).
|
|
258 | 258 | | {'shrinkers', [proper_shrink:shrinker()]}
|
259 | 259 | | {'noshrink', boolean()}
|
260 | 260 | | {'internal_type', raw_type()}
|
261 |
| - | {'internal_types', tuple() | maybe_improper_list(type(),type() | [])} |
| 261 | + | {'internal_types', tuple() | map() | maybe_improper_list(type(),type() | [])} |
262 | 262 | %% The items returned by 'remove' must be of this type.
|
263 | 263 | | {'get_length', fun((proper_gen:imm_instance()) -> length())}
|
264 | 264 | %% If this is a container type, this should return the number of elements
|
@@ -312,6 +312,8 @@ cook_outer(RawType) when is_tuple(RawType) ->
|
312 | 312 | tuple(tuple_to_list(RawType));
|
313 | 313 | cook_outer(RawType) when is_list(RawType) ->
|
314 | 314 | fixed_list(RawType); %% CAUTION: this must handle improper lists
|
| 315 | +cook_outer(RawType) when is_map(RawType) -> |
| 316 | + fixed_map(RawType); |
315 | 317 | cook_outer(RawType) -> %% default case (integers, floats, atoms, binaries, ...)
|
316 | 318 | exactly(RawType).
|
317 | 319 |
|
@@ -1124,6 +1126,63 @@ map() ->
|
1124 | 1126 | map(K, V) ->
|
1125 | 1127 | ?LET(L, list({K, V}), maps:from_list(L)).
|
1126 | 1128 |
|
| 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)). |
1127 | 1186 |
|
1128 | 1187 | %% @doc All Erlang terms (that PropEr can produce). For reasons of efficiency,
|
1129 | 1188 | %% functions are never produced as instances of this type.<br />
|
|
0 commit comments