diff --git a/include/proper.hrl b/include/proper.hrl
index 803408ae..fff62bcd 100644
--- a/include/proper.hrl
+++ b/include/proper.hrl
@@ -46,10 +46,10 @@
%% Basic types
%%------------------------------------------------------------------------------
--import(proper_types, [integer/2, float/2, atom/0, binary/0, binary/1,
- bitstring/0, bitstring/1, list/1, vector/2, union/1,
- weighted_union/1, tuple/1, loose_tuple/1, exactly/1,
- fixed_list/1, function/2, map/2, any/0]).
+-import(proper_types, [integer/2, float/2, atom/0, existing_atom/0, binary/0,
+ binary/1, bitstring/0, bitstring/1, list/1, vector/2,
+ union/1, weighted_union/1, tuple/1, loose_tuple/1,
+ exactly/1, fixed_list/1, function/2, map/2, any/0]).
%%------------------------------------------------------------------------------
diff --git a/src/proper.erl b/src/proper.erl
index 06ab14b9..60969728 100644
--- a/src/proper.erl
+++ b/src/proper.erl
@@ -297,6 +297,10 @@
%%%
`{stop_nodes, boolean()}'
%%% Specifies whether parallel PropEr should stop the nodes after running a property
%%% or not. Defaults to true.
+%%% `{default_atom_generator, atom | existing_atom}'
+%%% Declares the type of atom generator to use in the {@link proper_types:any/0. `any()'}
+%%% generator to be either {@link proper_types:atom/0. `atom()'} or
+%%% {@link proper_types:existing_atom/0. `existing_atom()'}.
%%%
%%%
%%% == Spec testing ==
@@ -539,6 +543,7 @@
| 'quiet'
| 'verbose'
| pos_integer()
+ | {'default_atom_generator', 'atom' | 'existing_atom'}
| {'constraint_tries',pos_integer()}
| {'false_positive_mfas',false_positive_mfas()}
| {'max_shrinks',non_neg_integer()}
@@ -569,6 +574,7 @@
constraint_tries = 50 :: pos_integer(),
expect_fail = false :: boolean(),
any_type :: {'type', proper_types:type()} | 'undefined',
+ default_atom_generator = atom :: 'atom' | 'existing_atom',
spec_timeout = infinity :: timeout(),
skip_mfas = [] :: [mfa()],
false_positive_mfas :: false_positive_mfas(),
@@ -744,8 +750,10 @@ global_state_init_size_seed(Size, Seed) ->
-spec global_state_init(opts()) -> 'ok'.
global_state_init(#opts{start_size = StartSize, constraint_tries = CTries,
search_strategy = Strategy, search_steps = SearchSteps,
- any_type = AnyType, seed = Seed, numworkers = NumWorkers} = Opts) ->
+ any_type = AnyType, seed = Seed, numworkers = NumWorkers,
+ default_atom_generator = DefaultAtomGen} = Opts) ->
clean_garbage(),
+ put('$default_atom_generator', DefaultAtomGen),
put('$size', StartSize - 1),
put('$left', 0),
put('$search_strategy', Strategy),
@@ -772,6 +780,7 @@ global_state_reset(#opts{start_size = StartSize} = Opts) ->
global_state_erase() ->
proper_typeserver:stop(),
proper_arith:rand_stop(),
+ erase('$default_atom_generator'),
erase('$any_type'),
erase('$constraint_tries'),
erase('$left'),
@@ -1039,6 +1048,8 @@ parse_opt(UserOpt, Opts) ->
N when is_integer(N) ->
?VALIDATE_OPT(?POS_INTEGER(N), Opts#opts{numtests = N});
%% tuple options, sorted on tag
+ {default_atom_generator,G} ->
+ ?VALIDATE_OPT(G =:= atom orelse G =:= existing_atom, Opts#opts{default_atom_generator = G});
{constraint_tries,N} ->
?VALIDATE_OPT(?POS_INTEGER(N), Opts#opts{constraint_tries = N});
{false_positive_mfas,F} ->
diff --git a/src/proper_gen.erl b/src/proper_gen.erl
index 6f603917..b7c34e11 100644
--- a/src/proper_gen.erl
+++ b/src/proper_gen.erl
@@ -46,6 +46,8 @@
any_gen/1, native_type_gen/2, safe_weighted_union_gen/1,
safe_union_gen/1]).
+-export([existing_atom_gen/0]).
+
%% Public API types
-export_type([instance/0, seed/0, size/0]).
%% Internal types
@@ -418,6 +420,27 @@ atom_gen(Size) ->
atom_rev(Atom) ->
{'$used', atom_to_list(Atom), Atom}.
+%% @private
+-spec existing_atom_gen() -> atom().
+%% We make sure we never clash with internal atoms by ignoring atoms starting with
+%% the character '$'.
+existing_atom_gen() ->
+ existing_atom_gen(10).
+
+existing_atom_gen(0) ->
+ '';
+existing_atom_gen(N) ->
+ Index = proper_arith:rand_int(0, erlang:system_info(atom_count) - 1),
+ Atom = get_existing_atom(Index),
+ case Atom =:= '' orelse hd(atom_to_list(Atom)) =/= $$ of
+ true -> Atom;
+ false -> existing_atom_gen(N - 1)
+ end.
+
+-define(ATOM_TERM_BIN(Index), <<131, 75, Index:24>>).
+get_existing_atom(Index) ->
+ binary_to_term(?ATOM_TERM_BIN(Index)).
+
%% @private
-spec binary_gen(size()) -> proper_types:type().
binary_gen(Size) ->
@@ -594,7 +617,7 @@ any_gen(Size) ->
-spec real_any_gen(size()) -> imm_instance().
real_any_gen(0) ->
SimpleTypes = [proper_types:integer(), proper_types:float(),
- proper_types:atom()],
+ proper_types:default_atom()],
union_gen(SimpleTypes);
real_any_gen(Size) ->
FreqChoices = [{?ANY_SIMPLE_PROB,simple}, {?ANY_BINARY_PROB,binary},
diff --git a/src/proper_gen_next.erl b/src/proper_gen_next.erl
index 569d47c4..cf176021 100644
--- a/src/proper_gen_next.erl
+++ b/src/proper_gen_next.erl
@@ -315,7 +315,7 @@ float_gen_sa({'$type', TypeProps}) ->
%% List
is_list_type(Type) ->
- has_same_generator(Type, proper_types:list(proper_types:atom())).
+ has_same_generator(Type, proper_types:list(proper_types:default_atom())).
list_choice(empty, Temp) ->
C = ?RANDOM_MOD:uniform(),
@@ -426,7 +426,7 @@ vector_gen_sa(Type) ->
%% atom
is_atom_type(Type) ->
- has_same_generator(Type, proper_types:atom()).
+ has_same_generator(Type, proper_types:default_atom()).
atom_gen_sa(_AtomType) ->
StringType = proper_types:list(proper_types:integer(0, 255)),
diff --git a/src/proper_types.erl b/src/proper_types.erl
index 676aff22..1323c85f 100644
--- a/src/proper_types.erl
+++ b/src/proper_types.erl
@@ -139,7 +139,7 @@
-module(proper_types).
-export([is_inst/2, is_inst/3]).
--export([integer/2, float/2, atom/0, binary/0, binary/1, bitstring/0,
+-export([integer/2, float/2, atom/0, existing_atom/0, binary/0, binary/1, bitstring/0,
bitstring/1, list/1, vector/2, union/1, weighted_union/1, tuple/1,
loose_tuple/1, exactly/1, fixed_list/1, function/2, map/0, map/2,
any/0, shrink_list/1, safe_union/1, safe_weighted_union/1]).
@@ -160,6 +160,7 @@
native_type/2, distlist/3, with_parameter/3, with_parameters/2,
parameter/1, parameter/2]).
-export([le/2]).
+-export([default_atom/0]).
%% Public API types
-export_type([type/0, raw_type/0]).
@@ -674,6 +675,28 @@ atom() ->
{is_instance, fun atom_is_instance/1}
]).
+%% @doc All existing atoms. All atoms used internally by PropEr start with a
+%% '`$'', so such atoms will never be produced as instances of this type. You
+%% should also refrain from using such atoms in your code, to avoid a potential
+%% clash.
+%% Instances do not shrink.
+-spec existing_atom() -> proper_types:type().
+existing_atom() ->
+ ?BASIC([
+ {generator, fun proper_gen:existing_atom_gen/0},
+ {reverse_gen, fun proper_gen:atom_rev/1},
+ {is_instance, fun atom_is_instance/1},
+ {noshrink, true}
+ ]).
+
+%% @private
+-spec default_atom() -> proper_types:type().
+default_atom() ->
+ case get('$default_atom_generator') of
+ existing_atom -> existing_atom();
+ _ -> atom()
+ end.
+
atom_is_instance(X) ->
is_atom(X)
%% We return false for atoms starting with '$', since these are
@@ -1127,7 +1150,7 @@ map(K, V) ->
%% type if you are certain that you need it.
-spec any() -> proper_types:type().
any() ->
- AllTypes = [integer(),float(),atom(),bitstring(),?LAZY(loose_tuple(any())),
+ AllTypes = [integer(),float(),default_atom(),bitstring(),?LAZY(loose_tuple(any())),
?LAZY(list(any())), ?LAZY(map(any(), any()))],
subtype([{generator, fun proper_gen:any_gen/1}], union(AllTypes)).
diff --git a/src/proper_typeserver.erl b/src/proper_typeserver.erl
index 4a59abbf..1b55970d 100644
--- a/src/proper_typeserver.erl
+++ b/src/proper_typeserver.erl
@@ -187,9 +187,9 @@
%% CAUTION: all these must be sorted
-define(STD_TYPES_0,
- [any,arity,atom,binary,bitstring,bool,boolean,byte,char,float,integer,
- list,neg_integer,nil,non_neg_integer,number,pos_integer,string,term,
- timeout]).
+ [any,arity,atom,binary,bitstring,bool,boolean,byte,char,existing_atom,
+ float,integer,list,neg_integer,nil,non_neg_integer,number,pos_integer,
+ string,term,timeout]).
-define(HARD_ADTS,
%% gb_trees:iterator and gb_sets:iterator are NOT hardcoded
[{{array,0},array}, {{array,1},proper_array},
diff --git a/test/proper_tests.erl b/test/proper_tests.erl
index eaa5f469..3df88a50 100644
--- a/test/proper_tests.erl
+++ b/test/proper_tests.erl
@@ -1398,6 +1398,20 @@ sampleshrink_test_() ->
?_shrinksTo([a], Gen)},
?_test(proper_gen:sampleshrink(Gen))]}].
+existing_atom_test() ->
+ _ = proper_gen:pick(proper_types:existing_atom()),
+ N = erlang:system_info(atom_count),
+ {ok, Atom} = proper_gen:pick(proper_types:existing_atom()),
+ ?assert(erlang:is_atom(Atom)),
+ ?assertEqual(N, erlang:system_info(atom_count)).
+
+default_atom_test() ->
+ _ = proper:quickcheck(?FORALL(_, any(), true),
+ [1, {default_atom_generator, existing_atom}]),
+ N = erlang:system_info(atom_count),
+ ?assert(proper:quickcheck(?FORALL(_, any(), true),
+ [{default_atom_generator, existing_atom}])),
+ ?assertEqual(N, erlang:system_info(atom_count)).
%%------------------------------------------------------------------------------
%% Performance tests