From 0ec0dec48192d4e110040276f15afce92200a0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= 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 | 30 ++++ lib/debugger/src/dbg_iload.erl | 11 ++ lib/debugger/test/Makefile | 1 + lib/debugger/test/maybe_SUITE.erl | 290 ++++++++++++++++++++++++++++++ 4 files changed, 332 insertions(+) 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 2d736898d897..4353716bfe0b 100644 --- a/lib/debugger/src/dbg_ieval.erl +++ b/lib/debugger/src/dbg_ieval.erl @@ -755,6 +755,25 @@ expr({'orelse',Line,E1,E2}, Bs0, Ieval) -> exception(error, {badarg,Val}, Bs, Ieval) end; +%% Maybe statement without else +expr({'maybe',Line,Es}, Bs, Ieval) -> + try + seq(Es, Bs, Ieval#ieval{line=Line}) + catch + throw:{bad_maybe_match,Val} -> + {value,Val,Bs} + end; + +%% Maybe statement with else +expr({'maybe',Line,Es,Cs}, Bs, Ieval) -> + try seq(Es, Bs, Ieval#ieval{line=Line}) of + {value,Val,_} -> + {value,Val,Bs} + catch + throw:{bad_maybe_match,Val} -> + case_clauses(Val, Cs, Bs, else_clause, Ieval#ieval{line=Line}) + end; + %% Matching expression expr({match,Line,Lhs,Rhs0}, Bs0, Ieval0) -> Ieval = Ieval0#ieval{line=Line}, @@ -766,6 +785,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 -> + throw({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 4649f7cef4f1..7499a0d94b93 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 015b5f9c29c5..45a2cdc74f7f 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 000000000000..1279fc0a4730 --- /dev/null +++ b/lib/debugger/test/maybe_SUITE.erl @@ -0,0 +1,290 @@ +%% +%% %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]). + +all() -> + [{group,p}]. + +groups() -> + [{p,[parallel], + [basic,nested]}]. + +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}. + +%% Utility functions. + +do_something(V, M) -> + map_get(id(V), M). + +id(X) -> X.