File 3651-Teach-the-debugger-to-handle-the-maybe-expression.patch of Package erlang
From 39f160fc55b70c1ea5a755aee698791838cb28b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Tue, 29 Aug 2023 09:06:35 +0200
Subject: [PATCH] Teach the debugger to handle the maybe expression
Fixes #7410
---
lib/debugger/src/dbg_ieval.erl | 37 +++-
lib/debugger/src/dbg_iload.erl | 11 ++
lib/debugger/test/Makefile | 1 +
lib/debugger/test/maybe_SUITE.erl | 305 ++++++++++++++++++++++++++++++
4 files changed, 352 insertions(+), 2 deletions(-)
create mode 100644 lib/debugger/test/maybe_SUITE.erl
diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl
index 2d736898d8..978a605dae 100644
--- a/lib/debugger/src/dbg_ieval.erl
+++ b/lib/debugger/src/dbg_ieval.erl
@@ -620,8 +620,12 @@ seq([E|Es], Bs0, Ieval) ->
{skip,Bs} ->
seq(Es, Bs, Ieval);
Bs1 ->
- {value,_,Bs} = expr(E, Bs1, Ieval#ieval{top=false}),
- seq(Es, Bs, Ieval)
+ case expr(E, Bs1, Ieval#ieval{top=false}) of
+ {value,_,Bs} ->
+ seq(Es, Bs, Ieval);
+ {bad_maybe_match,_}=Bad ->
+ Bad
+ end
end;
seq([], Bs, _) ->
{value,true,Bs}.
@@ -755,6 +759,24 @@ expr({'orelse',Line,E1,E2}, Bs0, Ieval) ->
exception(error, {badarg,Val}, Bs, Ieval)
end;
+%% Maybe statement without else
+expr({'maybe',Line,Es}, Bs, Ieval) ->
+ case seq(Es, Bs, Ieval#ieval{line=Line}) of
+ {bad_maybe_match,Val} ->
+ {value,Val,Bs};
+ {value,_,_}=Other ->
+ Other
+ end;
+
+%% Maybe statement with else
+expr({'maybe',Line,Es,Cs}, Bs, Ieval) ->
+ case seq(Es, Bs, Ieval#ieval{line=Line}) of
+ {bad_maybe_match,Val} ->
+ case_clauses(Val, Cs, Bs, else_clause, Ieval#ieval{line=Line});
+ {value,_,_}=Other ->
+ Other
+ end;
+
%% Matching expression
expr({match,Line,Lhs,Rhs0}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
@@ -766,6 +788,17 @@ expr({match,Line,Lhs,Rhs0}, Bs0, Ieval0) ->
exception(error, {badmatch,Rhs}, Bs1, Ieval)
end;
+%% Conditional match expression (?=)
+expr({maybe_match,Line,Lhs,Rhs0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,Rhs,Bs1} = expr(Rhs0, Bs0, Ieval#ieval{top=false}),
+ case match(Lhs, Rhs, Bs1) of
+ {match,Bs} ->
+ {value,Rhs,Bs};
+ nomatch ->
+ {bad_maybe_match,Rhs}
+ end;
+
%% Construct a fun
expr({make_fun,Line,Name,Cs}, Bs, #ieval{module=Module}=Ieval) ->
Arity = length(element(3,hd(Cs))),
diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl
index 4649f7cef4..7499a0d94b 100644
--- a/lib/debugger/src/dbg_iload.erl
+++ b/lib/debugger/src/dbg_iload.erl
@@ -512,6 +512,13 @@ expr({'receive',Anno,Cs0,To0,ToEs0}, Lc, St) ->
ToEs1 = exprs(ToEs0, Lc, St),
Cs1 = icr_clauses(Cs0, Lc, St),
{'receive',ln(Anno),Cs1,To1,ToEs1};
+expr({'maybe',Anno,Es0}, Lc, St) ->
+ Es1 = exprs(Es0, Lc, St),
+ {'maybe',ln(Anno),Es1};
+expr({'maybe',Anno,Es0,{'else',_ElseAnno,Cs0}}, Lc, St) ->
+ Es1 = exprs(Es0, Lc, St),
+ Cs1 = icr_clauses(Cs0, Lc, St),
+ {'maybe',ln(Anno),Es1,Cs1};
expr({'fun',Anno,{clauses,Cs0}}, _Lc, St) ->
%% New R10B-2 format (abstract_v2).
Cs = fun_clauses(Cs0, St),
@@ -619,6 +626,10 @@ expr({match,Anno,P0,E0}, _Lc, St) ->
E1 = expr(E0, false, St),
P1 = pattern(P0, St),
{match,ln(Anno),P1,E1};
+expr({maybe_match,Anno,P0,E0}, _Lc, St) ->
+ E1 = expr(E0, false, St),
+ P1 = pattern(P0, St),
+ {maybe_match,ln(Anno),P1,E1};
expr({op,Anno,Op,A0}, _Lc, St) ->
A1 = expr(A0, false, St),
{op,ln(Anno),Op,[A1]};
diff --git a/lib/debugger/test/Makefile b/lib/debugger/test/Makefile
index 015b5f9c29..45a2cdc74f 100644
--- a/lib/debugger/test/Makefile
+++ b/lib/debugger/test/Makefile
@@ -47,6 +47,7 @@ MODULES= \
lc_SUITE \
line_number_SUITE \
map_SUITE \
+ maybe_SUITE \
overridden_bif_SUITE \
record_SUITE \
trycatch_SUITE \
diff --git a/lib/debugger/test/maybe_SUITE.erl b/lib/debugger/test/maybe_SUITE.erl
new file mode 100644
index 0000000000..564c99a719
--- /dev/null
+++ b/lib/debugger/test/maybe_SUITE.erl
@@ -0,0 +1,305 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(maybe_SUITE).
+-feature(maybe_expr, enable).
+-include_lib("common_test/include/ct.hrl").
+
+-export([all/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2]).
+-export([basic/1, nested/1, in_try_catch/1]).
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,[parallel],
+ [basic,nested,in_try_catch]}].
+
+init_per_testcase(_Case, Config) ->
+ test_lib:interpret(?MODULE),
+ Config.
+
+end_per_testcase(_Case, _Config) ->
+ ok.
+
+init_per_suite(Config) ->
+ test_lib:interpret(?MODULE),
+ true = lists:member(?MODULE, int:interpreted()),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+-record(value, {v}).
+
+basic(_Config) ->
+ {ok,42,fish} = basic_1(0, #{0 => {ok,42}, 42 => {ok,fish}}),
+ error = basic_1(0, #{0 => {ok,42}, 42 => {error,whatever}}),
+ error = basic_1(0, #{0 => {ok,42}, 42 => error}),
+ error = basic_1(0, #{0 => error}),
+ error = basic_1(0, #{0 => {error,whatever}}),
+ some_value = basic_1(0, #{0 => #value{v=some_value}}),
+ {'EXIT',{{else_clause,something_wrong},[_|_]}} = catch basic_1(0, #{0 => something_wrong}),
+
+ {ok,life,"universe",everything} = basic_2(0, #{0 => {ok,life},
+ life => "universe",
+ "universe" => {ok,everything}}),
+ error = basic_2(0, #{0 => {ok,life},
+ life => "universe",
+ "universe" => error}),
+ {'EXIT',{{badmatch,not_a_list},[_|_]}} = catch basic_2(0, #{0 => {ok,life},
+ life => not_a_list}),
+ {'EXIT',{{else_clause,not_ok},[_|_]}} = catch basic_2(0, #{0 => {ok,life},
+ life => "universe",
+ "universe" => not_ok}),
+ {'EXIT',{{else_clause,not_ok},[_|_]}} = catch basic_2(0, #{0 => not_ok}),
+
+ {ok,42,fish,dolphins} = basic_3(0, #{0 => {ok,42}, 42 => {ok,fish},
+ fish => {ok,#value{v=dolphins}}}),
+ {error,whatever} = basic_3(0, #{0 => {ok,42}, 42 => {error,whatever}}),
+ failed = basic_3(0, #{0 => {ok,42}, 42 => failed}),
+ failed_early = basic_3(0, #{0 => failed_early}),
+
+ y = maybe nomatch ?= id(x) else _ -> y end,
+ y = maybe nomatch ?= id(x) else _ -> x, y end,
+
+ x = maybe nomatch ?= id(x) else E1 -> E1 end,
+
+ 6 = maybe X1 = 2+2, X1+2 end,
+ 6 = maybe X2 = 2+2, X2+2 else {error, T} -> T end,
+ {"llo", "hello", "hello"} = maybe Y1 = "he"++X3=Z1 ?= "hello", {X3,Y1,Z1} end,
+ {"llo", "hello", "llo"} = maybe Y2 = "he"++(X4=Z2) ?= "hello", {X4,Y2,Z2} end,
+
+ whatever = maybe
+ AlwaysMatching ?= id(whatever),
+ AlwaysMatching
+ else
+ E2 -> E2
+ end,
+
+ <<0>> = basic_4(id({<<0>>})),
+
+ ok.
+
+basic_1(V0, M) ->
+ Res = basic_1a(V0, M),
+ {wrapped,Res} = basic_1b(V0, M),
+ {wrapped,Res} = basic_1c(V0, M),
+ Res.
+
+basic_1a(V0, M) ->
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ {ok,V2} ?= do_something(V1, M),
+ {ok,V1,V2}
+ else
+ {error,_} ->
+ error;
+ error ->
+ error;
+ #value{v=V} ->
+ V
+ end.
+
+basic_1b(V0, M) ->
+ Result =
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ {ok,V2} ?= do_something(V1, M),
+ {ok,V1,V2}
+ else
+ {error,_} ->
+ error;
+ error ->
+ error;
+ #value{v=V} ->
+ V
+ end,
+ {wrapped,Result}.
+
+basic_1c(V0, M) ->
+ OK = id(ok),
+ Error = id(error),
+ Result =
+ maybe
+ {OK,V1} ?= do_something(V0, M),
+ {OK,V2} ?= do_something(V1, M),
+ {OK,V1,V2}
+ else
+ {Error,_} ->
+ Error;
+ Error ->
+ Error;
+ #value{v=V} ->
+ V
+ end,
+ {wrapped,Result}.
+
+basic_2(V0, M) ->
+ Res = basic_2a(V0, M),
+ {wrapped,Res} = basic_2b(V0, M),
+ Res.
+
+basic_2a(V0, M) ->
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ V2 = [_|_] = do_something(V1, M),
+ {ok,V3} ?= do_something(V2, M),
+ {ok,V1,V2,V3}
+ else
+ {error,_} ->
+ error;
+ error ->
+ error;
+ #value{v=V} ->
+ V
+ end.
+
+basic_2b(V0, M) ->
+ Result =
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ V2 = [_|_] = do_something(V1, M),
+ {ok,V3} ?= do_something(V2, M),
+ {ok,V1,V2,V3}
+ else
+ {error,_} ->
+ error;
+ error ->
+ error;
+ #value{v=V} ->
+ V
+ end,
+ _ = id(0),
+ {wrapped,Result}.
+
+basic_3(V0, M) ->
+ Res = basic_3a(V0, M),
+ {wrapped,Res} = basic_3b(V0, M),
+ Res.
+
+basic_3a(V0, M) ->
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ {ok,V2} ?= do_something(V1, M),
+ {ok,#value{v=V3}} ?= do_something(V2, M),
+ {ok,V1,V2,V3}
+ end.
+
+basic_3b(V0, M) ->
+ Result =
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ {ok,V2} ?= do_something(V1, M),
+ {ok,#value{v=V3}} ?= do_something(V2, M),
+ {ok,V1,V2,V3}
+ end,
+ {wrapped,Result}.
+
+basic_4({X}) ->
+ maybe
+ <<_:(ok)>> ?= X
+ end.
+
+nested(_Config) ->
+ {outer_fail,not_ok} = nested_1(0, #{0 => not_ok}),
+ {x,{error,inner}} = nested_1(0, #{0 => {ok,x}, x => {error,inner}}),
+ {outer_fail,{unexpected,not_error}} = nested_1(0, #{0 => {ok,x}, x => not_error}),
+ ok.
+
+nested_1(V0, M) ->
+ Res = nested_1a(V0, M),
+ {wrapped,Res} = nested_1b(V0, M),
+ {wrapped,Res} = nested_1c(V0, M),
+ Res.
+
+nested_1a(V0, M) ->
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ V2 = {error,_} ?=
+ maybe
+ {error, _} ?= id(do_something(V1, M))
+ else
+ Unexpected -> {unexpected, Unexpected}
+ end,
+ {V1,V2}
+ else
+ Res -> {outer_fail,Res}
+ end.
+
+nested_1b(V0, M) ->
+ Result =
+ maybe
+ {ok,V1} ?= do_something(V0, M),
+ V2 = {error,_} ?=
+ maybe
+ {error, _} ?= id(do_something(V1, M))
+ else
+ Unexpected -> {unexpected, Unexpected}
+ end,
+ {V1,V2}
+ else
+ Res -> {outer_fail,Res}
+ end,
+ {wrapped,Result}.
+
+nested_1c(V0, M) ->
+ Result =
+ maybe
+ R ?= maybe
+ {ok,V1} ?= do_something(V0, M),
+ {error,_} = V2 ?=
+ maybe
+ {error, _} ?= id(do_something(V1, M))
+ else
+ Unexpected -> {unexpected, Unexpected}
+ end,
+ {V1,V2}
+ else
+ Res -> {outer_fail,Res}
+ end,
+ R
+ else
+ Var -> Var
+ end,
+ {wrapped,Result}.
+
+in_try_catch(_Config) ->
+ Ref = make_ref(),
+ try
+ in_try_catch_1(Ref),
+ ct:failed(should_not_succeed)
+ catch
+ {bad_maybe_match,Ref} ->
+ ok
+ end.
+
+in_try_catch_1(Ref) ->
+ maybe
+ throw({bad_maybe_match,Ref})
+ end.
+
+%% Utility functions.
+
+do_something(V, M) ->
+ map_get(id(V), M).
+
+id(X) -> X.
--
2.35.3