1%%% Copyright 2010-2013 Manolis Papadakis <manopapad@gmail.com>, 2%%% Eirini Arvaniti <eirinibob@gmail.com> 3%%% and Kostis Sagonas <kostis@cs.ntua.gr> 4%%% 5%%% This file is part of PropEr. 6%%% 7%%% PropEr is free software: you can redistribute it and/or modify 8%%% it under the terms of the GNU General Public License as published by 9%%% the Free Software Foundation, either version 3 of the License, or 10%%% (at your option) any later version. 11%%% 12%%% PropEr is distributed in the hope that it will be useful, 13%%% but WITHOUT ANY WARRANTY; without even the implied warranty of 14%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15%%% GNU General Public License for more details. 16%%% 17%%% You should have received a copy of the GNU General Public License 18%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>. 19 20%%% @copyright 2010-2013 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas 21%%% @version {@version} 22%%% @author Manolis Papadakis 23 24%%% @doc Type manipulation functions and predefined types. 25%%% 26%%% == Basic types == 27%%% This module defines all the basic types of the PropEr type system as 28%%% functions. See the <a href="#index">function index</a> for an overview. 29%%% 30%%% Types can be combined in tuples or lists to produce other types. Exact 31%%% values (such as exact numbers, atoms, binaries and strings) can be combined 32%%% with types inside such structures, like in this example of the type of a 33%%% tagged tuple: ``{'result', integer()}''. 34%%% 35%%% When including the PropEr header file, all 36%%% <a href="#index">API functions</a> of this module are automatically 37%%% imported, unless `PROPER_NO_IMPORTS' is defined. 38%%% 39%%% == Customized types == 40%%% The following operators can be applied to basic types in order to produce 41%%% new ones: 42%%% 43%%% <dl> 44%%% <dt>`?LET(<Xs>, <Xs_type>, <In>)'</dt> 45%%% <dd>To produce an instance of this type, all appearances of the variables 46%%% in `<Xs>' are replaced inside `<In>' by their corresponding values in a 47%%% randomly generated instance of `<Xs_type>'. It's OK for the `<In>' part to 48%%% evaluate to a type - in that case, an instance of the inner type is 49%%% generated recursively.</dd> 50%%% <dt>`?SUCHTHAT(<X>, <Type>, <Condition>)'</dt> 51%%% <dd>This produces a specialization of `<Type>', which only includes those 52%%% members of `<Type>' that satisfy the constraint `<Condition>' - that is, 53%%% those members for which the function `fun(<X>) -> <Condition> end' returns 54%%% `true'. If the constraint is very strict - that is, only a small 55%%% percentage of instances of `<Type>' pass the test - it will take a lot of 56%%% tries for the instance generation subsystem to randomly produce a valid 57%%% instance. This will result in slower testing, and testing may even be 58%%% stopped short, in case the `constraint_tries' limit is reached (see the 59%%% "Options" section in the documentation of the {@link proper} module). If 60%%% this is the case, it would be more appropriate to generate valid instances 61%%% of the specialized type using the `?LET' macro. Also make sure that even 62%%% small instances can satisfy the constraint, since PropEr will only try 63%%% small instances at the start of testing. If this is not possible, you can 64%%% instruct PropEr to start at a larger size, by supplying a suitable value 65%%% for the `start_size' option (see the "Options" section in the 66%%% documentation of the {@link proper} module).</dd> 67%%% <dt>`?SUCHTHATMAYBE(<X>, <Type>, <Condition>)'</dt> 68%%% <dd>Equivalent to the `?SUCHTHAT' macro, but the constraint `<Condition>' 69%%% is considered non-strict: if the `constraint_tries' limit is reached, the 70%%% generator will just return an instance of `<Type>' instead of failing, 71%%% even if that instance doesn't satisfy the constraint.</dd> 72%%% <dt>`?SHRINK(<Generator>, <List_of_alt_gens>)'</dt> 73%%% <dd>This creates a type whose instances are generated by evaluating the 74%%% statement block `<Generator>' (this may evaluate to a type, which will 75%%% then be generated recursively). If an instance of such a type is to be 76%%% shrunk, the generators in `<List_of_alt_gens>' are first run to produce 77%%% hopefully simpler instances of the type. Thus, the generators in the 78%%% second argument should be simpler than the default. The simplest ones 79%%% should be at the front of the list, since those are the generators 80%%% preferred by the shrinking subsystem. Like the main `<Generator>', the 81%%% alternatives may also evaluate to a type, which is generated recursively. 82%%% </dd> 83%%% <dt>`?LETSHRINK(<List_of_variables>, <List_of_types>, <Generator>)'</dt> 84%%% <dd>This is created by combining a `?LET' and a `?SHRINK' macro. Instances 85%%% are generated by applying a randomly generated list of values inside 86%%% `<Generator>' (just like a `?LET', with the added constraint that the 87%%% variables and types must be provided in a list - alternatively, 88%%% `<List_of_types>' may be a list or vector type). When shrinking instances 89%%% of such a type, the sub-instances that were combined to produce it are 90%%% first tried in place of the failing instance.</dd> 91%%% <dt>`?LAZY(<Generator>)'</dt> 92%%% <dd>This construct returns a type whose only purpose is to delay the 93%%% evaluation of `<Generator>' (`<Generator>' can return a type, which will 94%%% be generated recursively). Using this, you can simulate the lazy 95%%% generation of instances: 96%%% ``` stream() -> ?LAZY(frequency([ {1,[]}, {3,[0|stream()]} ])). ''' 97%%% The above type produces lists of zeroes with an average length of 3. Note 98%%% that, had we not enclosed the generator with a `?LAZY' macro, the 99%%% evaluation would continue indefinitely, due to the eager evaluation of 100%%% the Erlang language.</dd> 101%%% <dt>`non_empty(<List_or_binary_type>)'</dt> 102%%% <dd>See the documentation for {@link non_empty/1}.</dd> 103%%% <dt>`noshrink(<Type>)'</dt> 104%%% <dd>See the documentation for {@link noshrink/1}.</dd> 105%%% <dt>`default(<Default_value>, <Type>)'</dt> 106%%% <dd>See the documentation for {@link default/2}.</dd> 107%%% <dt>`with_parameter(<Parameter>, <Value>, <Type>)'</dt> 108%%% <dd>See the documentation for {@link with_parameter/3}.</dd> 109%%% <dt>`with_parameters(<Param_value_pairs>, <Type>)'</dt> 110%%% <dd>See the documentation for {@link with_parameters/2}.</dd> 111%%% </dl> 112%%% 113%%% == Size manipulation == 114%%% The following operators are related to the `size' parameter, which controls 115%%% the maximum size of produced instances. The actual size of a produced 116%%% instance is chosen randomly, but can never exceed the value of the `size' 117%%% parameter at the moment of generation. A more accurate definition is the 118%%% following: the maximum instance of `size S' can never be smaller than the 119%%% maximum instance of `size S-1'. The actual size of an instance is measured 120%%% differently for each type: the actual size of a list is its length, while 121%%% the actual size of a tree may be the number of its internal nodes. Some 122%%% types, e.g. unions, have no notion of size, thus their generation is not 123%%% influenced by the value of `size'. The `size' parameter starts at 1 and 124%%% grows automatically during testing. 125%%% 126%%% <dl> 127%%% <dt>`?SIZED(<S>, <Generator>)'</dt> 128%%% <dd>Creates a new type, whose instances are produced by replacing all 129%%% appearances of the `<S>' parameter inside the statement block 130%%% `<Generator>' with the value of the `size' parameter. It's OK for the 131%%% `<Generator>' to return a type - in that case, an instance of the inner 132%%% type is generated recursively.</dd> 133%%% <dt>`resize(<New_size>, <Type>)'</dt> 134%%% <dd>See the documentation for {@link resize/2}.</dd> 135%%% </dl> 136 137-module(proper_types). 138-export([is_inst/2, is_inst/3]). 139 140-export([integer/2, float/2, atom/0, binary/0, binary/1, bitstring/0, 141 bitstring/1, list/1, vector/2, union/1, weighted_union/1, tuple/1, 142 loose_tuple/1, exactly/1, fixed_list/1, function/2, any/0, 143 shrink_list/1, safe_union/1, safe_weighted_union/1]). 144-export([integer/0, non_neg_integer/0, pos_integer/0, neg_integer/0, range/2, 145 float/0, non_neg_float/0, number/0, boolean/0, byte/0, char/0, 146 list/0, tuple/0, string/0, wunion/1, term/0, timeout/0, arity/0]). 147-export([int/0, nat/0, largeint/0, real/0, bool/0, choose/2, elements/1, 148 oneof/1, frequency/1, return/1, default/2, orderedlist/1, function0/1, 149 function1/1, function2/1, function3/1, function4/1, 150 weighted_default/2]). 151-export([resize/2, non_empty/1, noshrink/1]). 152 153-export([cook_outer/1, is_type/1, equal_types/2, is_raw_type/1, to_binary/1, 154 from_binary/1, get_prop/2, find_prop/2, safe_is_instance/2, 155 is_instance/2, unwrap/1, weakly/1, strongly/1, satisfies_all/2, 156 new_type/2, subtype/2]). 157-export([lazy/1, sized/1, bind/3, shrinkwith/2, add_constraint/3, 158 native_type/2, distlist/3, with_parameter/3, with_parameters/2, 159 parameter/1, parameter/2]). 160-export([le/2]). 161 162-export_type([type/0, raw_type/0, extint/0, extnum/0]). 163 164-include("proper_internal.hrl"). 165 166 167%%------------------------------------------------------------------------------ 168%% Comparison with erl_types 169%%------------------------------------------------------------------------------ 170 171%% Missing types 172%% ------------------- 173%% will do: 174%% records, maybe_improper_list(T,S), nonempty_improper_list(T,S) 175%% maybe_improper_list(), maybe_improper_list(T), iolist, iodata 176%% don't need: 177%% nonempty_{list,string,maybe_improper_list} 178%% won't do: 179%% pid, port, ref, identifier, none, no_return, module, mfa, node 180%% array, dict, digraph, set, gb_tree, gb_set, queue, tid 181 182%% Missing type information 183%% ------------------------ 184%% bin types: 185%% other unit sizes? what about size info? 186%% functions: 187%% generally some fun, unspecified number of arguments but specified 188%% return type 189%% any: 190%% doesn't cover functions and improper lists 191 192 193%%------------------------------------------------------------------------------ 194%% Type declaration macros 195%%------------------------------------------------------------------------------ 196 197-define(BASIC(PropList), new_type(PropList,basic)). 198-define(WRAPPER(PropList), new_type(PropList,wrapper)). 199-define(CONSTRUCTED(PropList), new_type(PropList,constructed)). 200-define(CONTAINER(PropList), new_type(PropList,container)). 201-define(SUBTYPE(Type,PropList), subtype(PropList,Type)). 202 203 204%%------------------------------------------------------------------------------ 205%% Types 206%%------------------------------------------------------------------------------ 207 208-type type_kind() :: 'basic' | 'wrapper' | 'constructed' | 'container' | atom(). 209-type instance_test() :: fun((proper_gen:imm_instance()) -> boolean()) 210 | {'typed', 211 fun((proper_types:type(), 212 proper_gen:imm_instance()) -> boolean())}. 213-type index() :: pos_integer(). 214%% @alias 215-type value() :: term(). 216%% @private_type 217%% @alias 218-type extint() :: integer() | 'inf'. 219%% @private_type 220%% @alias 221-type extnum() :: number() | 'inf'. 222-type constraint_fun() :: fun((proper_gen:instance()) -> boolean()). 223 224-opaque type() :: {'$type', [type_prop()]}. 225%% A type of the PropEr type system 226%% @type raw_type(). You can consider this as an equivalent of {@type type()}. 227-type raw_type() :: type() | [raw_type()] | loose_tuple(raw_type()) | term(). 228-type type_prop_name() :: 'kind' | 'generator' | 'reverse_gen' | 'parts_type' 229 | 'combine' | 'alt_gens' | 'shrink_to_parts' 230 | 'size_transform' | 'is_instance' | 'shrinkers' 231 | 'noshrink' | 'internal_type' | 'internal_types' 232 | 'get_length' | 'split' | 'join' | 'get_indices' 233 | 'remove' | 'retrieve' | 'update' | 'constraints' 234 | 'parameters' | 'env' | 'subenv'. 235 236-type type_prop_value() :: term(). 237-type type_prop() :: 238 {'kind', type_kind()} 239 | {'generator', proper_gen:generator()} 240 | {'reverse_gen', proper_gen:reverse_gen()} 241 | {'parts_type', type()} 242 | {'combine', proper_gen:combine_fun()} 243 | {'alt_gens', proper_gen:alt_gens()} 244 | {'shrink_to_parts', boolean()} 245 | {'size_transform', fun((size()) -> size())} 246 | {'is_instance', instance_test()} 247 | {'shrinkers', [proper_shrink:shrinker()]} 248 | {'noshrink', boolean()} 249 | {'internal_type', raw_type()} 250 | {'internal_types', tuple() | maybe_improper_list(type(),type() | [])} 251 %% The items returned by 'remove' must be of this type. 252 | {'get_length', fun((proper_gen:imm_instance()) -> length())} 253 %% If this is a container type, this should return the number of elements 254 %% it contains. 255 | {'split', fun((proper_gen:imm_instance()) -> [proper_gen:imm_instance()]) 256 | fun((length(),proper_gen:imm_instance()) -> 257 {proper_gen:imm_instance(),proper_gen:imm_instance()})} 258 %% If present, the appropriate form depends on whether get_length is 259 %% defined: if get_length is undefined, this must be in the one-argument 260 %% form (e.g. a tree should be split into its subtrees), else it must be 261 %% in the two-argument form (e.g. a list should be split in two at the 262 %% index provided). 263 | {'join', fun((proper_gen:imm_instance(),proper_gen:imm_instance()) -> 264 proper_gen:imm_instance())} 265 | {'get_indices', fun((proper_types:type(), 266 proper_gen:imm_instance()) -> [index()])} 267 %% If this is a container type, this should return a list of indices we 268 %% can use to remove or insert elements from the given instance. 269 | {'remove', fun((index(),proper_gen:imm_instance()) -> 270 proper_gen:imm_instance())} 271 | {'retrieve', fun((index(), proper_gen:imm_instance() | tuple() 272 | maybe_improper_list(type(),type() | [])) -> 273 value() | type())} 274 | {'update', fun((index(),value(),proper_gen:imm_instance()) -> 275 proper_gen:imm_instance())} 276 | {'constraints', [{constraint_fun(), boolean()}]} 277 %% A list of constraints on instances of this type: each constraint is a 278 %% tuple of a fun that must return 'true' for each valid instance and a 279 %% boolean field that specifies whether the condition is strict. 280 | {'parameters', [{atom(),value()}]} 281 | {'env', term()} 282 | {'subenv', term()}. 283 284 285%%------------------------------------------------------------------------------ 286%% Type manipulation functions 287%%------------------------------------------------------------------------------ 288 289%% TODO: We shouldn't need the fully qualified type name in the range of these 290%% functions. 291 292%% @private 293%% TODO: just cook/1 ? 294-spec cook_outer(raw_type()) -> proper_types:type(). 295cook_outer(Type = {'$type',_Props}) -> 296 Type; 297cook_outer(RawType) -> 298 if 299 is_tuple(RawType) -> tuple(tuple_to_list(RawType)); 300 %% CAUTION: this must handle improper lists 301 is_list(RawType) -> fixed_list(RawType); 302 %% default case (covers integers, floats, atoms, binaries, ...): 303 true -> exactly(RawType) 304 end. 305 306%% @private 307-spec is_type(term()) -> boolean(). 308is_type({'$type',_Props}) -> 309 true; 310is_type(_) -> 311 false. 312 313%% @private 314-spec equal_types(proper_types:type(), proper_types:type()) -> boolean(). 315equal_types(SameType, SameType) -> 316 true; 317equal_types(_, _) -> 318 false. 319 320%% @private 321-spec is_raw_type(term()) -> boolean(). 322is_raw_type({'$type',_TypeProps}) -> 323 true; 324is_raw_type(X) -> 325 if 326 is_tuple(X) -> is_raw_type_list(tuple_to_list(X)); 327 is_list(X) -> is_raw_type_list(X); 328 true -> false 329 end. 330 331-spec is_raw_type_list(maybe_improper_list()) -> boolean(). 332%% CAUTION: this must handle improper lists 333is_raw_type_list(List) -> 334 proper_arith:safe_any(fun is_raw_type/1, List). 335 336%% @private 337-spec to_binary(proper_types:type()) -> binary(). 338to_binary(Type) -> 339 term_to_binary(Type). 340 341%% @private 342%% TODO: restore: -spec from_binary(binary()) -> proper_types:type(). 343from_binary(Binary) -> 344 binary_to_term(Binary). 345 346-spec type_from_list([type_prop()]) -> proper_types:type(). 347type_from_list(KeyValueList) -> 348 {'$type',KeyValueList}. 349 350-spec add_prop(type_prop_name(), type_prop_value(), proper_types:type()) -> 351 proper_types:type(). 352add_prop(PropName, Value, {'$type',Props}) -> 353 {'$type',lists:keystore(PropName, 1, Props, {PropName, Value})}. 354 355-spec add_props([type_prop()], proper_types:type()) -> proper_types:type(). 356add_props(PropList, {'$type',OldProps}) -> 357 {'$type', lists:foldl(fun({N,_}=NV,Acc) -> 358 lists:keystore(N, 1, Acc, NV) 359 end, OldProps, PropList)}. 360 361-spec append_to_prop(type_prop_name(), type_prop_value(), 362 proper_types:type()) -> proper_types:type(). 363append_to_prop(PropName, Value, {'$type',Props}) -> 364 Val = case lists:keyfind(PropName, 1, Props) of 365 {PropName, V} -> 366 V; 367 _ -> 368 [] 369 end, 370 {'$type', lists:keystore(PropName, 1, Props, 371 {PropName, lists:reverse([Value|Val])})}. 372 373-spec append_list_to_prop(type_prop_name(), [type_prop_value()], 374 proper_types:type()) -> proper_types:type(). 375append_list_to_prop(PropName, List, {'$type',Props}) -> 376 {PropName, Val} = lists:keyfind(PropName, 1, Props), 377 {'$type', lists:keystore(PropName, 1, Props, {PropName, Val++List})}. 378 379%% @private 380-spec get_prop(type_prop_name(), proper_types:type()) -> type_prop_value(). 381get_prop(PropName, {'$type',Props}) -> 382 {_PropName, Val} = lists:keyfind(PropName, 1, Props), 383 Val. 384 385%% @private 386-spec find_prop(type_prop_name(), proper_types:type()) -> 387 {'ok',type_prop_value()} | 'error'. 388find_prop(PropName, {'$type',Props}) -> 389 case lists:keyfind(PropName, 1, Props) of 390 {PropName, Value} -> 391 {ok, Value}; 392 _ -> 393 error 394 end. 395 396%% @private 397-spec new_type([type_prop()], type_kind()) -> proper_types:type(). 398new_type(PropList, Kind) -> 399 Type = type_from_list(PropList), 400 add_prop(kind, Kind, Type). 401 402%% @private 403-spec subtype([type_prop()], proper_types:type()) -> proper_types:type(). 404%% TODO: should the 'is_instance' function etc. be reset for subtypes? 405subtype(PropList, Type) -> 406 add_props(PropList, Type). 407 408%% @private 409-spec is_inst(proper_gen:instance(), raw_type()) -> 410 boolean() | {'error',{'typeserver',term()}}. 411is_inst(Instance, RawType) -> 412 is_inst(Instance, RawType, 10). 413 414%% @private 415-spec is_inst(proper_gen:instance(), raw_type(), size()) -> 416 boolean() | {'error',{'typeserver',term()}}. 417is_inst(Instance, RawType, Size) -> 418 proper:global_state_init_size(Size), 419 Result = safe_is_instance(Instance, RawType), 420 proper:global_state_erase(), 421 Result. 422 423%% @private 424-spec safe_is_instance(proper_gen:imm_instance(), raw_type()) -> 425 boolean() | {'error',{'typeserver',term()}}. 426safe_is_instance(ImmInstance, RawType) -> 427 try is_instance(ImmInstance, RawType) catch 428 throw:{'$typeserver',SubReason} -> {error, {typeserver,SubReason}} 429 end. 430 431%% @private 432-spec is_instance(proper_gen:imm_instance(), raw_type()) -> boolean(). 433%% TODO: If the second argument is not a type, let it pass (don't even check for 434%% term equality?) - if it's a raw type, don't cook it, instead recurse 435%% into it. 436is_instance(ImmInstance, RawType) -> 437 CleanInstance = proper_gen:clean_instance(ImmInstance), 438 Type = cook_outer(RawType), 439 (case get_prop(kind, Type) of 440 wrapper -> wrapper_test(ImmInstance, Type); 441 constructed -> constructed_test(ImmInstance, Type); 442 _ -> false 443 end 444 orelse 445 case find_prop(is_instance, Type) of 446 {ok,{typed, IsInstance}} -> IsInstance(Type, ImmInstance); 447 {ok,IsInstance} -> IsInstance(ImmInstance); 448 error -> false 449 end) 450 andalso weakly(satisfies_all(CleanInstance, Type)). 451 452-spec wrapper_test(proper_gen:imm_instance(), proper_types:type()) -> boolean(). 453wrapper_test(ImmInstance, Type) -> 454 %% TODO: check if it's actually a raw type that's returned? 455 lists:any(fun(T) -> is_instance(ImmInstance, T) end, unwrap(Type)). 456 457%% @private 458%% TODO: restore:-spec unwrap(proper_types:type()) -> [proper_types:type(),...]. 459%% TODO: check if it's actually a raw type that's returned? 460unwrap(Type) -> 461 RawInnerTypes = proper_gen:alt_gens(Type) ++ [proper_gen:normal_gen(Type)], 462 [cook_outer(T) || T <- RawInnerTypes]. 463 464-spec constructed_test(proper_gen:imm_instance(), proper_types:type()) -> 465 boolean(). 466constructed_test({'$used',ImmParts,ImmInstance}, Type) -> 467 PartsType = get_prop(parts_type, Type), 468 Combine = get_prop(combine, Type), 469 is_instance(ImmParts, PartsType) andalso 470 begin 471 %% TODO: check if it's actually a raw type that's returned? 472 %% TODO: move construction code to proper_gen 473 %% TODO: non-type => should we check for strict term equality? 474 RawInnerType = Combine(proper_gen:clean_instance(ImmParts)), 475 is_instance(ImmInstance, RawInnerType) 476 end; 477constructed_test({'$to_part',ImmInstance}, Type) -> 478 PartsType = get_prop(parts_type, Type), 479 get_prop(shrink_to_parts, Type) =:= true andalso 480 %% TODO: we reject non-container types 481 get_prop(kind, PartsType) =:= container andalso 482 case {find_prop(internal_type,PartsType), 483 find_prop(internal_types,PartsType)} of 484 {{ok,EachPartType},error} -> 485 %% The parts are in a list or a vector. 486 is_instance(ImmInstance, EachPartType); 487 {error,{ok,PartTypesList}} -> 488 %% The parts are in a fixed list. 489 %% TODO: It should always be a proper list. 490 lists:any(fun(T) -> is_instance(ImmInstance,T) end, PartTypesList) 491 end; 492constructed_test(_CleanInstance, _Type) -> 493 %% TODO: can we do anything better? 494 false. 495 496%% @private 497-spec weakly({boolean(),boolean()}) -> boolean(). 498weakly({B1,_B2}) -> B1. 499 500%% @private 501-spec strongly({boolean(),boolean()}) -> boolean(). 502strongly({_B1,B2}) -> B2. 503 504-spec satisfies(proper_gen:instance(), {constraint_fun(),boolean()}) 505 -> {boolean(),boolean()}. 506satisfies(Instance, {Test,false}) -> 507 {true,Test(Instance)}; 508satisfies(Instance, {Test,true}) -> 509 Result = Test(Instance), 510 {Result,Result}. 511 512%% @private 513-spec satisfies_all(proper_gen:instance(), proper_types:type()) -> 514 {boolean(),boolean()}. 515satisfies_all(Instance, Type) -> 516 case find_prop(constraints, Type) of 517 {ok, Constraints} -> 518 L = [satisfies(Instance, C) || C <- Constraints], 519 {L1,L2} = lists:unzip(L), 520 {lists:all(fun(B) -> B end, L1), lists:all(fun(B) -> B end, L2)}; 521 error -> 522 {true,true} 523 end. 524 525 526%%------------------------------------------------------------------------------ 527%% Type definition functions 528%%------------------------------------------------------------------------------ 529 530%% @private 531-spec lazy(proper_gen:nosize_generator()) -> proper_types:type(). 532lazy(Gen) -> 533 ?WRAPPER([ 534 {generator, Gen} 535 ]). 536 537%% @private 538-spec sized(proper_gen:sized_generator()) -> proper_types:type(). 539sized(Gen) -> 540 ?WRAPPER([ 541 {generator, Gen} 542 ]). 543 544%% @private 545-spec bind(raw_type(), proper_gen:combine_fun(), boolean()) -> 546 proper_types:type(). 547bind(RawPartsType, Combine, ShrinkToParts) -> 548 PartsType = cook_outer(RawPartsType), 549 ?CONSTRUCTED([ 550 {parts_type, PartsType}, 551 {combine, Combine}, 552 {shrink_to_parts, ShrinkToParts} 553 ]). 554 555%% @private 556-spec shrinkwith(proper_gen:nosize_generator(), proper_gen:alt_gens()) -> 557 proper_types:type(). 558shrinkwith(Gen, DelaydAltGens) -> 559 ?WRAPPER([ 560 {generator, Gen}, 561 {alt_gens, DelaydAltGens} 562 ]). 563 564%% @private 565-spec add_constraint(raw_type(), constraint_fun(), boolean()) -> 566 proper_types:type(). 567add_constraint(RawType, Condition, IsStrict) -> 568 Type = cook_outer(RawType), 569 append_to_prop(constraints, {Condition,IsStrict}, Type). 570 571%% @private 572-spec native_type(mod_name(), string()) -> proper_types:type(). 573native_type(Mod, TypeStr) -> 574 ?WRAPPER([ 575 {generator, fun() -> proper_gen:native_type_gen(Mod,TypeStr) end} 576 ]). 577 578 579%%------------------------------------------------------------------------------ 580%% Basic types 581%%------------------------------------------------------------------------------ 582 583%% @doc All integers between `Low' and `High', bounds included. 584%% `Low' and `High' must be Erlang expressions that evaluate to integers, with 585%% `Low =< High'. Additionally, `Low' and `High' may have the value `inf', in 586%% which case they represent minus infinity and plus infinity respectively. 587%% Instances shrink towards 0 if `Low =< 0 =< High', or towards the bound with 588%% the smallest absolute value otherwise. 589-spec integer(extint(), extint()) -> proper_types:type(). 590integer(Low, High) -> 591 ?BASIC([ 592 {env, {Low, High}}, 593 {generator, {typed, fun integer_gen/2}}, 594 {is_instance, {typed, fun integer_is_instance/2}}, 595 {shrinkers, [fun number_shrinker/3]} 596 ]). 597 598integer_gen(Type, Size) -> 599 {Low, High} = get_prop(env, Type), 600 proper_gen:integer_gen(Size, Low, High). 601 602integer_is_instance(Type, X) -> 603 {Low, High} = get_prop(env, Type), 604 is_integer(X) andalso le(Low, X) andalso le(X, High). 605 606number_shrinker(X, Type, S) -> 607 {Low, High} = get_prop(env, Type), 608 proper_shrink:number_shrinker(X, Low, High, S). 609 610%% @doc All floats between `Low' and `High', bounds included. 611%% `Low' and `High' must be Erlang expressions that evaluate to floats, with 612%% `Low =< High'. Additionally, `Low' and `High' may have the value `inf', in 613%% which case they represent minus infinity and plus infinity respectively. 614%% Instances shrink towards 0.0 if `Low =< 0.0 =< High', or towards the bound 615%% with the smallest absolute value otherwise. 616-spec float(extnum(), extnum()) -> proper_types:type(). 617float(Low, High) -> 618 ?BASIC([ 619 {env, {Low, High}}, 620 {generator, {typed, fun float_gen/2}}, 621 {is_instance, {typed, fun float_is_instance/2}}, 622 {shrinkers, [fun number_shrinker/3]} 623 ]). 624 625float_gen(Type, Size) -> 626 {Low, High} = get_prop(env, Type), 627 proper_gen:float_gen(Size, Low, High). 628 629float_is_instance(Type, X) -> 630 {Low, High} = get_prop(env, Type), 631 is_float(X) andalso le(Low, X) andalso le(X, High). 632 633%% @private 634-spec le(extnum(), extnum()) -> boolean(). 635le(inf, _B) -> true; 636le(_A, inf) -> true; 637le(A, B) -> A =< B. 638 639%% @doc All atoms. All atoms used internally by PropEr start with a '`$'', so 640%% such atoms will never be produced as instances of this type. You should also 641%% refrain from using such atoms in your code, to avoid a potential clash. 642%% Instances shrink towards the empty atom, ''. 643-spec atom() -> proper_types:type(). 644atom() -> 645 ?WRAPPER([ 646 {generator, fun proper_gen:atom_gen/1}, 647 {reverse_gen, fun proper_gen:atom_rev/1}, 648 {size_transform, fun(Size) -> erlang:min(Size,255) end}, 649 {is_instance, fun atom_is_instance/1} 650 ]). 651 652atom_is_instance(X) -> 653 is_atom(X) 654 %% We return false for atoms starting with '$', since these are 655 %% atoms used internally and never produced by the atom generator. 656 andalso (X =:= '' orelse hd(atom_to_list(X)) =/= $$). 657 658%% @doc All binaries. Instances shrink towards the empty binary, `<<>>'. 659-spec binary() -> proper_types:type(). 660binary() -> 661 ?WRAPPER([ 662 {generator, fun proper_gen:binary_gen/1}, 663 {reverse_gen, fun proper_gen:binary_rev/1}, 664 {is_instance, fun erlang:is_binary/1} 665 ]). 666 667%% @doc All binaries with a byte size of `Len'. 668%% `Len' must be an Erlang expression that evaluates to a non-negative integer. 669%% Instances shrink towards binaries of zeroes. 670-spec binary(length()) -> proper_types:type(). 671binary(Len) -> 672 ?WRAPPER([ 673 {env, Len}, 674 {generator, {typed, fun binary_len_gen/1}}, 675 {reverse_gen, fun proper_gen:binary_rev/1}, 676 {is_instance, {typed, fun binary_len_is_instance/2}} 677 ]). 678 679binary_len_gen(Type) -> 680 Len = get_prop(env, Type), 681 proper_gen:binary_len_gen(Len). 682 683binary_len_is_instance(Type, X) -> 684 Len = get_prop(env, Type), 685 is_binary(X) andalso byte_size(X) =:= Len. 686 687%% @doc All bitstrings. Instances shrink towards the empty bitstring, `<<>>'. 688-spec bitstring() -> proper_types:type(). 689bitstring() -> 690 ?WRAPPER([ 691 {generator, fun proper_gen:bitstring_gen/1}, 692 {reverse_gen, fun proper_gen:bitstring_rev/1}, 693 {is_instance, fun erlang:is_bitstring/1} 694 ]). 695 696%% @doc All bitstrings with a bit size of `Len'. 697%% `Len' must be an Erlang expression that evaluates to a non-negative integer. 698%% Instances shrink towards bitstrings of zeroes 699-spec bitstring(length()) -> proper_types:type(). 700bitstring(Len) -> 701 ?WRAPPER([ 702 {env, Len}, 703 {generator, {typed, fun bitstring_len_gen/1}}, 704 {reverse_gen, fun proper_gen:bitstring_rev/1}, 705 {is_instance, {typed, fun bitstring_len_is_instance/2}} 706 ]). 707 708bitstring_len_gen(Type) -> 709 Len = get_prop(env, Type), 710 proper_gen:bitstring_len_gen(Len). 711 712bitstring_len_is_instance(Type, X) -> 713 Len = get_prop(env, Type), 714 is_bitstring(X) andalso bit_size(X) =:= Len. 715 716%% @doc All lists containing elements of type `ElemType'. 717%% Instances shrink towards the empty list, `[]'. 718-spec list(ElemType::raw_type()) -> proper_types:type(). 719% TODO: subtyping would be useful here (list, vector, fixed_list) 720list(RawElemType) -> 721 ElemType = cook_outer(RawElemType), 722 ?CONTAINER([ 723 {generator, {typed, fun list_gen/2}}, 724 {is_instance, {typed, fun list_is_instance/2}}, 725 {internal_type, ElemType}, 726 {get_length, fun erlang:length/1}, 727 {split, fun lists:split/2}, 728 {join, fun lists:append/2}, 729 {get_indices, fun list_get_indices/2}, 730 {remove, fun proper_arith:list_remove/2}, 731 {retrieve, fun lists:nth/2}, 732 {update, fun proper_arith:list_update/3} 733 ]). 734 735list_gen(Type, Size) -> 736 ElemType = get_prop(internal_type, Type), 737 proper_gen:list_gen(Size, ElemType). 738 739list_is_instance(Type, X) -> 740 ElemType = get_prop(internal_type, Type), 741 list_test(X, ElemType). 742 743%% @doc A type that generates exactly the list `List'. Instances shrink towards 744%% shorter sublists of the original list. 745-spec shrink_list([term()]) -> proper_types:type(). 746shrink_list(List) -> 747 ?CONTAINER([ 748 {env, List}, 749 {generator, {typed, fun shrink_list_gen/1}}, 750 {is_instance, {typed, fun shrink_list_is_instance/2}}, 751 {get_length, fun erlang:length/1}, 752 {split, fun lists:split/2}, 753 {join, fun lists:append/2}, 754 {get_indices, fun list_get_indices/2}, 755 {remove, fun proper_arith:list_remove/2} 756 ]). 757 758shrink_list_gen(Type) -> 759 get_prop(env, Type). 760 761shrink_list_is_instance(Type, X) -> 762 List = get_prop(env, Type), 763 is_sublist(X, List). 764 765-spec is_sublist([term()], [term()]) -> boolean(). 766is_sublist([], _) -> true; 767is_sublist(_, []) -> false; 768is_sublist([H|T1], [H|T2]) -> is_sublist(T1, T2); 769is_sublist(Slice, [_|T2]) -> is_sublist(Slice, T2). 770 771-spec list_test(proper_gen:imm_instance(), proper_types:type()) -> boolean(). 772list_test(X, ElemType) -> 773 is_list(X) andalso lists:all(fun(E) -> is_instance(E, ElemType) end, X). 774 775%% @private 776-spec list_get_indices(proper_gen:generator(), list()) -> [position()]. 777list_get_indices(_, List) -> 778 lists:seq(1, length(List)). 779 780%% @private 781%% This assumes that: 782%% - instances of size S are always valid instances of size >S 783%% - any recursive calls inside Gen are lazy 784-spec distlist(size(), proper_gen:sized_generator(), boolean()) -> 785 proper_types:type(). 786distlist(Size, Gen, NonEmpty) -> 787 ParentType = case NonEmpty of 788 true -> non_empty(list(Gen(Size))); 789 false -> list(Gen(Size)) 790 end, 791 ?SUBTYPE(ParentType, [ 792 {subenv, {Size, Gen, NonEmpty}}, 793 {generator, {typed, fun distlist_gen/1}} 794 ]). 795 796distlist_gen(Type) -> 797 {Size, Gen, NonEmpty} = get_prop(subenv, Type), 798 proper_gen:distlist_gen(Size, Gen, NonEmpty). 799 800%% @doc All lists of length `Len' containing elements of type `ElemType'. 801%% `Len' must be an Erlang expression that evaluates to a non-negative integer. 802-spec vector(length(), ElemType::raw_type()) -> proper_types:type(). 803vector(Len, RawElemType) -> 804 ElemType = cook_outer(RawElemType), 805 ?CONTAINER([ 806 {env, Len}, 807 {generator, {typed, fun vector_gen/1}}, 808 {is_instance, {typed, fun vector_is_instance/2}}, 809 {internal_type, ElemType}, 810 {get_indices, fun vector_get_indices/2}, 811 {retrieve, fun lists:nth/2}, 812 {update, fun proper_arith:list_update/3} 813 ]). 814 815vector_gen(Type) -> 816 Len = get_prop(env, Type), 817 ElemType = get_prop(internal_type, Type), 818 proper_gen:vector_gen(Len, ElemType). 819 820vector_is_instance(Type, X) -> 821 Len = get_prop(env, Type), 822 ElemType = get_prop(internal_type, Type), 823 is_list(X) 824 andalso length(X) =:= Len 825 andalso lists:all(fun(E) -> is_instance(E, ElemType) end, X). 826 827vector_get_indices(Type, _X) -> 828 lists:seq(1, get_prop(env, Type)). 829 830%% @doc The union of all types in `ListOfTypes'. `ListOfTypes' can't be empty. 831%% The random instance generator is equally likely to choose any one of the 832%% types in `ListOfTypes'. The shrinking subsystem will always try to shrink an 833%% instance of a type union to an instance of the first type in `ListOfTypes', 834%% thus you should write the simplest case first. 835-spec union(ListOfTypes::[raw_type(),...]) -> proper_types:type(). 836union(RawChoices) -> 837 Choices = [cook_outer(C) || C <- RawChoices], 838 ?BASIC([ 839 {env, Choices}, 840 {generator, {typed, fun union_gen/1}}, 841 {is_instance, {typed, fun union_is_instance/2}}, 842 {shrinkers, [fun union_shrinker_1/3, fun union_shrinker_2/3]} 843 ]). 844 845union_gen(Type) -> 846 Choices = get_prop(env,Type), 847 proper_gen:union_gen(Choices). 848 849union_is_instance(Type, X) -> 850 Choices = get_prop(env, Type), 851 lists:any(fun(C) -> is_instance(X, C) end, Choices). 852 853union_shrinker_1(X, Type, S) -> 854 Choices = get_prop(env, Type), 855 proper_shrink:union_first_choice_shrinker(X, Choices, S). 856 857union_shrinker_2(X, Type, S) -> 858 Choices = get_prop(env, Type), 859 proper_shrink:union_recursive_shrinker(X, Choices, S). 860 861%% @doc A specialization of {@link union/1}, where each type in `ListOfTypes' is 862%% assigned a frequency. Frequencies must be Erlang expressions that evaluate to 863%% positive integers. Types with larger frequencies are more likely to be chosen 864%% by the random instance generator. The shrinking subsystem will ignore the 865%% frequencies and try to shrink towards the first type in the list. 866-spec weighted_union(ListOfTypes::[{frequency(),raw_type()},...]) -> 867 proper_types:type(). 868weighted_union(RawFreqChoices) -> 869 CookFreqType = fun({Freq,RawType}) -> {Freq,cook_outer(RawType)} end, 870 FreqChoices = lists:map(CookFreqType, RawFreqChoices), 871 Choices = [T || {_F,T} <- FreqChoices], 872 ?SUBTYPE(union(Choices), [ 873 {subenv, FreqChoices}, 874 {generator, {typed, fun weighted_union_gen/1}} 875 ]). 876 877weighted_union_gen(Gen) -> 878 FreqChoices = get_prop(subenv, Gen), 879 proper_gen:weighted_union_gen(FreqChoices). 880 881%% @private 882-spec safe_union([raw_type(),...]) -> proper_types:type(). 883safe_union(RawChoices) -> 884 Choices = [cook_outer(C) || C <- RawChoices], 885 subtype( 886 [{subenv, Choices}, 887 {generator, {typed, fun safe_union_gen/1}}], 888 union(Choices)). 889 890safe_union_gen(Type) -> 891 Choices = get_prop(subenv, Type), 892 proper_gen:safe_union_gen(Choices). 893 894%% @private 895-spec safe_weighted_union([{frequency(),raw_type()},...]) -> 896 proper_types:type(). 897safe_weighted_union(RawFreqChoices) -> 898 CookFreqType = fun({Freq,RawType}) -> 899 {Freq,cook_outer(RawType)} end, 900 FreqChoices = lists:map(CookFreqType, RawFreqChoices), 901 Choices = [T || {_F,T} <- FreqChoices], 902 subtype([{subenv, FreqChoices}, 903 {generator, {typed, fun safe_weighted_union_gen/1}}], 904 union(Choices)). 905 906safe_weighted_union_gen(Type) -> 907 FreqChoices = get_prop(subenv, Type), 908 proper_gen:safe_weighted_union_gen(FreqChoices). 909 910%% @doc All tuples whose i-th element is an instance of the type at index i of 911%% `ListOfTypes'. Also written simply as a tuple of types. 912-spec tuple(ListOfTypes::[raw_type()]) -> proper_types:type(). 913tuple(RawFields) -> 914 Fields = [cook_outer(F) || F <- RawFields], 915 ?CONTAINER([ 916 {env, Fields}, 917 {generator, {typed, fun tuple_gen/1}}, 918 {is_instance, {typed, fun tuple_is_instance/2}}, 919 {internal_types, list_to_tuple(Fields)}, 920 {get_indices, fun tuple_get_indices/2}, 921 {retrieve, fun erlang:element/2}, 922 {update, fun tuple_update/3} 923 ]). 924 925tuple_gen(Type) -> 926 Fields = get_prop(env, Type), 927 proper_gen:tuple_gen(Fields). 928 929tuple_is_instance(Type, X) -> 930 Fields = get_prop(env, Type), 931 is_tuple(X) andalso fixed_list_test(tuple_to_list(X), Fields). 932 933tuple_get_indices(Type, _X) -> 934 lists:seq(1, length(get_prop(env, Type))). 935 936-spec tuple_update(index(), value(), tuple()) -> tuple(). 937tuple_update(Index, NewElem, Tuple) -> 938 setelement(Index, Tuple, NewElem). 939 940%% @doc Tuples whose elements are all of type `ElemType'. 941%% Instances shrink towards the 0-size tuple, `{}'. 942-spec loose_tuple(ElemType::raw_type()) -> proper_types:type(). 943loose_tuple(RawElemType) -> 944 ElemType = cook_outer(RawElemType), 945 ?WRAPPER([ 946 {env, ElemType}, 947 {generator, {typed, fun loose_tuple_gen/2}}, 948 {reverse_gen, {typed, fun loose_tuple_rev/2}}, 949 {is_instance, {typed, fun loose_tuple_is_instance/2}} 950 ]). 951 952loose_tuple_gen(Type, Size) -> 953 ElemType = get_prop(env, Type), 954 proper_gen:loose_tuple_gen(Size, ElemType). 955 956loose_tuple_rev(Type, X) -> 957 ElemType = get_prop(env, Type), 958 proper_gen:loose_tuple_rev(X, ElemType). 959 960loose_tuple_is_instance(Type, X) -> 961 ElemType = get_prop(env, Type), 962 is_tuple(X) andalso list_test(tuple_to_list(X), ElemType). 963 964%% @doc Singleton type consisting only of `E'. `E' must be an evaluated term. 965%% Also written simply as `E'. 966-spec exactly(term()) -> proper_types:type(). 967exactly(E) -> 968 ?BASIC([ 969 {env, E}, 970 {generator, {typed, fun exactly_gen/1}}, 971 {is_instance, {typed, fun exactly_is_instance/2}} 972 ]). 973 974exactly_gen(Type) -> 975 E = get_prop(env, Type), 976 proper_gen:exactly_gen(E). 977 978exactly_is_instance(Type, X) -> 979 E = get_prop(env, Type), 980 X =:= E. 981 982%% @doc All lists whose i-th element is an instance of the type at index i of 983%% `ListOfTypes'. Also written simply as a list of types. 984-spec fixed_list(ListOfTypes::maybe_improper_list(raw_type(),raw_type()|[])) -> 985 proper_types:type(). 986fixed_list(MaybeImproperRawFields) -> 987 %% CAUTION: must handle improper lists 988 {Fields, Internal, Len, Retrieve, Update} = 989 case proper_arith:cut_improper_tail(MaybeImproperRawFields) of 990 % TODO: have cut_improper_tail return the length and use it in test? 991 {ProperRawHead, ImproperRawTail} -> 992 HeadLen = length(ProperRawHead), 993 CookedHead = [cook_outer(F) || F <- ProperRawHead], 994 CookedTail = cook_outer(ImproperRawTail), 995 {{CookedHead,CookedTail}, 996 CookedHead ++ CookedTail, 997 HeadLen + 1, 998 fun(I,L) -> improper_list_retrieve(I, L, HeadLen) end, 999 fun(I,V,L) -> improper_list_update(I, V, L, HeadLen) end}; 1000 ProperRawFields -> 1001 LocalFields = [cook_outer(F) || F <- ProperRawFields], 1002 {LocalFields, 1003 LocalFields, 1004 length(ProperRawFields), 1005 fun lists:nth/2, 1006 fun proper_arith:list_update/3} 1007 end, 1008 ?CONTAINER([ 1009 {env, {Fields, Len}}, 1010 {generator, {typed, fun fixed_list_gen/1}}, 1011 {is_instance, {typed, fun fixed_list_is_instance/2}}, 1012 {internal_types, Internal}, 1013 {get_indices, fun fixed_list_get_indices/2}, 1014 {retrieve, Retrieve}, 1015 {update, Update} 1016 ]). 1017 1018fixed_list_gen(Type) -> 1019 {Fields, _} = get_prop(env, Type), 1020 proper_gen:fixed_list_gen(Fields). 1021 1022fixed_list_is_instance(Type, X) -> 1023 {Fields, _} = get_prop(env, Type), 1024 fixed_list_test(X, Fields). 1025 1026fixed_list_get_indices(Type, _X) -> 1027 {_, Len} = get_prop(env, Type), 1028 lists:seq(1, Len). 1029 1030-spec fixed_list_test(proper_gen:imm_instance(), 1031 [proper_types:type()] | {[proper_types:type()], 1032 proper_types:type()}) -> 1033 boolean(). 1034fixed_list_test(X, {ProperHead,ImproperTail}) -> 1035 is_list(X) andalso 1036 begin 1037 ProperHeadLen = length(ProperHead), 1038 proper_arith:head_length(X) >= ProperHeadLen andalso 1039 begin 1040 {XHead,XTail} = lists:split(ProperHeadLen, X), 1041 fixed_list_test(XHead, ProperHead) 1042 andalso is_instance(XTail, ImproperTail) 1043 end 1044 end; 1045fixed_list_test(X, ProperFields) -> 1046 is_list(X) 1047 andalso length(X) =:= length(ProperFields) 1048 andalso lists:all(fun({E,T}) -> is_instance(E, T) end, 1049 lists:zip(X, ProperFields)). 1050 1051%% TODO: Move these 2 functions to proper_arith? 1052-spec improper_list_retrieve(index(), nonempty_improper_list(value(),value()), 1053 pos_integer()) -> value(). 1054improper_list_retrieve(Index, List, HeadLen) -> 1055 case Index =< HeadLen of 1056 true -> lists:nth(Index, List); 1057 false -> lists:nthtail(HeadLen, List) 1058 end. 1059 1060-spec improper_list_update(index(), value(), 1061 nonempty_improper_list(value(),value()), 1062 pos_integer()) -> 1063 nonempty_improper_list(value(),value()). 1064improper_list_update(Index, Value, List, HeadLen) -> 1065 case Index =< HeadLen of 1066 %% TODO: This happens to work, but is not implied by list_update's spec. 1067 true -> proper_arith:list_update(Index, Value, List); 1068 false -> lists:sublist(List, HeadLen) ++ Value 1069 end. 1070 1071%% @doc All pure functions that map instances of `ArgTypes' to instances of 1072%% `RetType'. The syntax `function(Arity, RetType)' is also acceptable. 1073-spec function(ArgTypes::[raw_type()] | arity(), RetType::raw_type()) -> 1074 proper_types:type(). 1075function(Arity, RawRetType) when is_integer(Arity), Arity >= 0, Arity =< 255 -> 1076 RetType = cook_outer(RawRetType), 1077 ?BASIC([ 1078 {env, {Arity, RetType}}, 1079 {generator, {typed, fun function_gen/1}}, 1080 {is_instance, {typed, fun function_is_instance/2}} 1081 ]); 1082function(RawArgTypes, RawRetType) -> 1083 function(length(RawArgTypes), RawRetType). 1084 1085function_gen(Type) -> 1086 {Arity, RetType} = get_prop(env, Type), 1087 proper_gen:function_gen(Arity, RetType). 1088 1089function_is_instance(Type, X) -> 1090 {Arity, RetType} = get_prop(env, Type), 1091 is_function(X, Arity) 1092 %% TODO: what if it's not a function we produced? 1093 andalso equal_types(RetType, proper_gen:get_ret_type(X)). 1094 1095%% @doc All Erlang terms (that PropEr can produce). For reasons of efficiency, 1096%% functions are never produced as instances of this type.<br /> 1097%% CAUTION: Instances of this type are expensive to produce, shrink and instance- 1098%% check, both in terms of processing time and consumed memory. Only use this 1099%% type if you are certain that you need it. 1100-spec any() -> proper_types:type(). 1101any() -> 1102 AllTypes = [integer(),float(),atom(),bitstring(),?LAZY(loose_tuple(any())), 1103 ?LAZY(list(any()))], 1104 ?SUBTYPE(union(AllTypes), [ 1105 {generator, fun proper_gen:any_gen/1} 1106 ]). 1107 1108 1109%%------------------------------------------------------------------------------ 1110%% Type aliases 1111%%------------------------------------------------------------------------------ 1112 1113%% @equiv integer(inf, inf) 1114-spec integer() -> proper_types:type(). 1115integer() -> integer(inf, inf). 1116 1117%% @equiv integer(0, inf) 1118-spec non_neg_integer() -> proper_types:type(). 1119non_neg_integer() -> integer(0, inf). 1120 1121%% @equiv integer(1, inf) 1122-spec pos_integer() -> proper_types:type(). 1123pos_integer() -> integer(1, inf). 1124 1125%% @equiv integer(inf, -1) 1126-spec neg_integer() -> proper_types:type(). 1127neg_integer() -> integer(inf, -1). 1128 1129%% @equiv integer(Low, High) 1130-spec range(extint(), extint()) -> proper_types:type(). 1131range(Low, High) -> integer(Low, High). 1132 1133%% @equiv float(inf, inf) 1134-spec float() -> proper_types:type(). 1135float() -> float(inf, inf). 1136 1137%% @equiv float(0.0, inf) 1138-spec non_neg_float() -> proper_types:type(). 1139non_neg_float() -> float(0.0, inf). 1140 1141%% @equiv union([integer(), float()]) 1142-spec number() -> proper_types:type(). 1143number() -> union([integer(), float()]). 1144 1145%% @doc The atoms `true' and `false'. Instances shrink towards `false'. 1146-spec boolean() -> proper_types:type(). 1147boolean() -> union(['false', 'true']). 1148 1149%% @equiv integer(0, 255) 1150-spec byte() -> proper_types:type(). 1151byte() -> integer(0, 255). 1152 1153%% @equiv integer(0, 16#10ffff) 1154-spec char() -> proper_types:type(). 1155char() -> integer(0, 16#10ffff). 1156 1157%% @equiv list(any()) 1158-spec list() -> proper_types:type(). 1159list() -> list(any()). 1160 1161%% @equiv loose_tuple(any()) 1162-spec tuple() -> proper_types:type(). 1163tuple() -> loose_tuple(any()). 1164 1165%% @equiv list(char()) 1166-spec string() -> proper_types:type(). 1167string() -> list(char()). 1168 1169%% @equiv weighted_union(FreqChoices) 1170-spec wunion([{frequency(),raw_type()},...]) -> proper_types:type(). 1171wunion(FreqChoices) -> weighted_union(FreqChoices). 1172 1173%% @equiv any() 1174-spec term() -> proper_types:type(). 1175term() -> any(). 1176 1177%% @equiv union([non_neg_integer() | infinity]) 1178-spec timeout() -> proper_types:type(). 1179timeout() -> union([non_neg_integer(), 'infinity']). 1180 1181%% @equiv integer(0, 255) 1182-spec arity() -> proper_types:type(). 1183arity() -> integer(0, 255). 1184 1185 1186%%------------------------------------------------------------------------------ 1187%% QuickCheck compatibility types 1188%%------------------------------------------------------------------------------ 1189 1190%% @doc Small integers (bound by the current value of the `size' parameter). 1191%% Instances shrink towards `0'. 1192-spec int() -> proper_types:type(). 1193int() -> ?SIZED(Size, integer(-Size,Size)). 1194 1195%% @doc Small non-negative integers (bound by the current value of the `size' 1196%% parameter). Instances shrink towards `0'. 1197-spec nat() -> proper_types:type(). 1198nat() -> ?SIZED(Size, integer(0,Size)). 1199 1200%% @equiv integer() 1201-spec largeint() -> proper_types:type(). 1202largeint() -> integer(). 1203 1204%% @equiv float() 1205-spec real() -> proper_types:type(). 1206real() -> float(). 1207 1208%% @equiv boolean() 1209-spec bool() -> proper_types:type(). 1210bool() -> boolean(). 1211 1212%% @equiv integer(Low, High) 1213-spec choose(extint(), extint()) -> proper_types:type(). 1214choose(Low, High) -> integer(Low, High). 1215 1216%% @equiv union(Choices) 1217-spec elements([raw_type(),...]) -> proper_types:type(). 1218elements(Choices) -> union(Choices). 1219 1220%% @equiv union(Choices) 1221-spec oneof([raw_type(),...]) -> proper_types:type(). 1222oneof(Choices) -> union(Choices). 1223 1224%% @equiv weighted_union(Choices) 1225-spec frequency([{frequency(),raw_type()},...]) -> proper_types:type(). 1226frequency(FreqChoices) -> weighted_union(FreqChoices). 1227 1228%% @equiv exactly(E) 1229-spec return(term()) -> proper_types:type(). 1230return(E) -> exactly(E). 1231 1232%% @doc Adds a default value, `Default', to `Type'. 1233%% The default serves as a primary shrinking target for instances, while it 1234%% is also chosen by the random instance generation subsystem half the time. 1235-spec default(raw_type(), raw_type()) -> proper_types:type(). 1236default(Default, Type) -> 1237 union([Default, Type]). 1238 1239%% @doc All sorted lists containing elements of type `ElemType'. 1240%% Instances shrink towards the empty list, `[]'. 1241-spec orderedlist(ElemType::raw_type()) -> proper_types:type(). 1242orderedlist(RawElemType) -> 1243 ?LET(L, list(RawElemType), lists:sort(L)). 1244 1245%% @equiv function(0, RetType) 1246-spec function0(raw_type()) -> proper_types:type(). 1247function0(RetType) -> 1248 function(0, RetType). 1249 1250%% @equiv function(1, RetType) 1251-spec function1(raw_type()) -> proper_types:type(). 1252function1(RetType) -> 1253 function(1, RetType). 1254 1255%% @equiv function(2, RetType) 1256-spec function2(raw_type()) -> proper_types:type(). 1257function2(RetType) -> 1258 function(2, RetType). 1259 1260%% @equiv function(3, RetType) 1261-spec function3(raw_type()) -> proper_types:type(). 1262function3(RetType) -> 1263 function(3, RetType). 1264 1265%% @equiv function(4, RetType) 1266-spec function4(raw_type()) -> proper_types:type(). 1267function4(RetType) -> 1268 function(4, RetType). 1269 1270%% @doc A specialization of {@link default/2}, where `Default' and `Type' are 1271%% assigned weights to be considered by the random instance generator. The 1272%% shrinking subsystem will ignore the weights and try to shrink using the 1273%% default value. 1274-spec weighted_default({frequency(),raw_type()}, {frequency(),raw_type()}) -> 1275 proper_types:type(). 1276weighted_default(Default, Type) -> 1277 weighted_union([Default, Type]). 1278 1279 1280%%------------------------------------------------------------------------------ 1281%% Additional type specification functions 1282%%------------------------------------------------------------------------------ 1283 1284%% @doc Overrides the `size' parameter used when generating instances of 1285%% `Type' with `NewSize'. Has no effect on size-less types, such as unions. 1286%% Also, this will not affect the generation of any internal types contained in 1287%% `Type', such as the elements of a list - those will still be generated 1288%% using the test-wide value of `size'. One use of this function is to modify 1289%% types to produce instances that grow faster or slower, like so: 1290%% ```?SIZED(Size, resize(Size * 2, list(integer()))''' 1291%% The above specifies a list type that grows twice as fast as normal lists. 1292-spec resize(size(), Type::raw_type()) -> proper_types:type(). 1293resize(NewSize, RawType) -> 1294 Type = cook_outer(RawType), 1295 case find_prop(size_transform, Type) of 1296 {ok,Transform} -> 1297 add_prop(size_transform, fun(_S) -> Transform(NewSize) end, Type); 1298 error -> 1299 add_prop(size_transform, fun(_S) -> NewSize end, Type) 1300 end. 1301 1302%% @doc This is a predefined constraint that can be applied to random-length 1303%% list and binary types to ensure that the produced values are never empty. 1304%% 1305%% e.g. {@link list/0}, {@link string/0}, {@link binary/0}) 1306-spec non_empty(ListType::raw_type()) -> proper_types:type(). 1307non_empty(RawListType) -> 1308 ?SUCHTHAT(L, RawListType, L =/= [] andalso L =/= <<>>). 1309 1310%% @doc Creates a new type which is equivalent to `Type', but whose instances 1311%% are never shrunk by the shrinking subsystem. 1312-spec noshrink(Type::raw_type()) -> proper_types:type(). 1313noshrink(RawType) -> 1314 add_prop(noshrink, true, cook_outer(RawType)). 1315 1316%% @doc Associates the atom key `Parameter' with the value `Value' while 1317%% generating instances of `Type'. 1318-spec with_parameter(atom(), value(), Type::raw_type()) -> proper_types:type(). 1319with_parameter(Parameter, Value, RawType) -> 1320 with_parameters([{Parameter,Value}], RawType). 1321 1322%% @doc Similar to {@link with_parameter/3}, but accepts a list of 1323%% `{Parameter, Value}' pairs. 1324-spec with_parameters([{atom(),value()}], Type::raw_type()) -> 1325 proper_types:type(). 1326with_parameters(PVlist, RawType) -> 1327 Type = cook_outer(RawType), 1328 case find_prop(parameters, Type) of 1329 {ok,Params} when is_list(Params) -> 1330 append_list_to_prop(parameters, PVlist, Type); 1331 error -> 1332 add_prop(parameters, PVlist, Type) 1333 end. 1334 1335%% @doc Returns the value associated with `Parameter', or `Default' in case 1336%% `Parameter' is not associated with any value. 1337-spec parameter(atom(), value()) -> value(). 1338parameter(Parameter, Default) -> 1339 Parameters = 1340 case erlang:get('$parameters') of 1341 undefined -> []; 1342 List -> List 1343 end, 1344 proplists:get_value(Parameter, Parameters, Default). 1345 1346%% @equiv parameter(Parameter, undefined) 1347-spec parameter(atom()) -> value(). 1348parameter(Parameter) -> 1349 parameter(Parameter, undefined). 1350