Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/proper_erlang_abstract_code.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2135,7 +2135,7 @@ check_option(types, Term) ->
end;
check_option(records, Term) ->
try
lists:all(fun(R) -> is_record(R) end, Term)
lists:all(fun(R) -> proper_is_record(R) end, Term)
catch
_:_ ->
false
Expand Down Expand Up @@ -2171,7 +2171,7 @@ is_variable(T) ->

is_fa({F, A}) when is_atom(F), is_integer(A), A >= 0, A < 256 -> true.

is_record({R, Fs}) when is_atom(R) ->
proper_is_record({R, Fs}) when is_atom(R) ->
true = lists:all(fun erlang:is_atom/1, Fs).

eval_options([], State) ->
Expand Down
5 changes: 3 additions & 2 deletions src/proper_statem.erl
Original file line number Diff line number Diff line change
Expand Up @@ -767,15 +767,16 @@ pmap(F, L) ->
[command_list()]) -> [pid()].
spawn_jobs(F, L) ->
Parent = self(),
[spawn_link_cp(fun() -> Parent ! {self(),catch {ok,F(X)}} end) || X <- L].
[spawn_link_cp(fun() -> Parent ! {self(),try {ok,F(X)} catch _:R -> {error,R} end} end)
|| X <- L].

-spec await([pid()]) -> [parallel_history()].
await([]) -> [];
await([H|T]) ->
receive
{H, {ok, Res}} ->
[Res|await(T)];
{H, {'EXIT',_} = Err} ->
{H, {error, Err}} ->
_ = [exit(Pid, kill) || Pid <- T],
_ = [receive {P,_} -> d_ after 0 -> i_ end || P <- T],
erlang:error(Err)
Expand Down
2 changes: 1 addition & 1 deletion test/ets_counter.erl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ set_up() ->
ok.

clean_up() ->
catch ets:delete(counter).
try ets:delete(counter) catch _:_ -> ok end.

key() ->
elements(?KEYS).
Expand Down
6 changes: 3 additions & 3 deletions test/ets_statem_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ prop_ets() ->
?FORALL(Type, noshrink(table_type()),
?FORALL(Cmds, commands(?MODULE, initial_state(Type)),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [Type, public, named_table]),
{H,S,Res} = run_commands(?MODULE, Cmds),
clean_up(),
Expand All @@ -241,7 +241,7 @@ prop_parallel_ets() ->
?FORALL(Type, noshrink(table_type()),
?FORALL(Cmds, parallel_commands(?MODULE, initial_state(Type)),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [Type, public, named_table]),
{Seq,P,Res} = run_parallel_commands(?MODULE, Cmds),
?WHENFAIL(
Expand All @@ -254,7 +254,7 @@ prop_parallel_ets() ->
%%% Utility Functions

set_up() ->
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
Type = lists:nth(proper_arith:rand_int(1, 4),
[set, ordered_set, bag, duplicate_bag]),
?TAB = ets:new(?TAB, [Type, public, named_table]).
Expand Down
2 changes: 1 addition & 1 deletion test/proper_exported_types_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ not_handled() ->
R = [{M,T,A,proper_typeserver:demo_translate_type(M, stringify(T, A))}
|| {M,T,A} <- MTs],
{OKs,Errors} = lists:partition(fun type_translation_is_ok/1, R),
{[Inst || TGen <- OKs, (Inst = pick_instance(TGen)) =/= ok], length(Errors)}.
Copy link
Copy Markdown
Collaborator

@kostis kostis Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to understand the rationale for this change and what exactly is wrong with this code in general and in the upcoming OTP 29. To me, this code besides being "valid Erlang", is perfectly understandable in what it does and how it achieves it. Moreover, it's the last statement in the body of this function, so there is not even a chance that variables will be used after it. So why is it better to have to write a new function of six lines for it?

Also, I've noticed that the release notes for the pre-releases of 29 advertise a "new language feature" that encourages such code. I quote from that page:

By enabling the compr_assign feature, it is now possible to bind variables in a comprehensions. For example: [H || E <- List, H = erlang:phash2(E), H rem 10 =:= 0]

How does the PropEr code differ from this example and what am I missing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's due to an unfortunate combination of two language changes:

  1. Exporting bindings from subexpressions is deprecated. In this case the lhs of (Inst = ...) =/= ok triggers that. Plain matches don't count as subexpressions.
  2. Previously you could write [Y || X <- L, Y = f(X), predicate(Y)] and it would compile but give a bad filter exception at runtime. The other change, enabled by compr_assign, makes those bindings work.

I've experimented a bit and found a shorter workaround: instead of having a match (Y = f(X)) to bind the intermediate value, use a single-element generator (Y <- [f(X)]). I'm pushing an update with that change.

{[Inst || TGen <- OKs, Inst <- [pick_instance(TGen)], Inst =/= ok], length(Errors)}.

pick_instance({M,T,A,{ok,Gen}}) ->
{ok,Inst} = proper_gen:pick(Gen),
Expand Down
6 changes: 3 additions & 3 deletions test/targeted_fsm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ non_empty_ets(S) ->
prop_random() ->
?FORALL(Cmds, proper_fsm:commands(?MODULE),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [set, public, named_table]),
{H, _S, Res} = proper_fsm:run_commands(?MODULE, Cmds),
ets:delete(?TAB),
Expand All @@ -103,7 +103,7 @@ prop_targeted() ->
?FORALL_TARGETED(
Cmds, proper_fsm:commands(?MODULE),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [set, public, named_table]),
{H, {_, S}, Res} = proper_fsm:run_commands(?MODULE, Cmds),
ets:delete(?TAB),
Expand All @@ -116,7 +116,7 @@ prop_targeted_init() ->
?FORALL_TARGETED(
Cmds, proper_fsm:commands(?MODULE, {empty_ets, []}),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [set, public, named_table]),
{H, {_, S}, Res} = proper_fsm:run_commands(?MODULE, Cmds),
ets:delete(?TAB),
Expand Down
6 changes: 3 additions & 3 deletions test/targeted_statem.erl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ postcondition(_S, _C, _R) -> true.
prop_random() ->
?FORALL(Cmds, commands(?MODULE),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [set, public, named_table]),
{_H, _S, Res} = run_commands(?MODULE, Cmds),
ets:delete(?TAB),
Expand All @@ -96,7 +96,7 @@ prop_random() ->
prop_targeted() ->
?FORALL_TARGETED(Cmds, commands(?MODULE),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [set, public, named_table]),
{_H, S, Res} = run_commands(?MODULE, Cmds),
ets:delete(?TAB),
Expand All @@ -107,7 +107,7 @@ prop_targeted() ->
prop_targeted_init() ->
?FORALL_TARGETED(Cmds, commands(?MODULE, []),
begin
catch ets:delete(?TAB),
try ets:delete(?TAB) catch _:_ -> ok end,
?TAB = ets:new(?TAB, [set, public, named_table]),
{_H, S, Res} = run_commands(?MODULE, Cmds),
ets:delete(?TAB),
Expand Down
Loading