File 3505-Implement-the-compiler-part-of-EEP-49.patch of Package erlang
From dfcfdce368a337c719baf16b7a37d1ca5bdfedf8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Mon, 1 Nov 2021 06:24:06 +0100
Subject: [PATCH 05/12] Implement the compiler part of EEP 49
---
lib/compiler/src/v3_core.erl | 106 ++++++++++
lib/compiler/test/Makefile | 6 +-
lib/compiler/test/beam_ssa_SUITE.erl | 4 +-
lib/compiler/test/beam_type_SUITE.erl | 4 +-
lib/compiler/test/compile_SUITE.erl | 3 +-
lib/compiler/test/match_SUITE.erl | 4 +-
lib/compiler/test/maybe_SUITE.erl | 273 ++++++++++++++++++++++++++
lib/compiler/test/test_lib.erl | 1 +
lib/compiler/test/warnings_SUITE.erl | 28 ++-
lib/stdlib/src/erl_expand_records.erl | 11 ++
lib/stdlib/src/erl_lint.erl | 17 ++
lib/stdlib/src/erl_parse.yrl | 22 ++-
lib/stdlib/src/erl_pp.erl | 14 +-
lib/stdlib/src/erl_scan.erl | 5 +
lib/stdlib/test/epp_SUITE.erl | 3 +
lib/stdlib/test/erl_lint_SUITE.erl | 84 +++++++-
lib/stdlib/test/erl_pp_SUITE.erl | 27 ++-
17 files changed, 593 insertions(+), 19 deletions(-)
create mode 100644 lib/compiler/test/maybe_SUITE.erl
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 139828084d..974d3413b1 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -623,6 +623,34 @@ exprs([E0|Es0], St0) ->
{Eps ++ [E1] ++ Es1,St2};
exprs([], St) -> {[],St}.
+%% exprs([Expr], State) -> {[Cexpr],State}.
+%% Flatten top-level exprs while handling maybe_match operators.
+
+maybe_match_exprs([{maybe_match,L,P0,E0}|Es0], Fail, St0) ->
+ {Es1,St1} = maybe_match_exprs(Es0, Fail, St0),
+ {C,St2} =
+ case Es1 of
+ [] ->
+ {AllName,StInt} = new_var_name(St1),
+ All = {var,L,AllName},
+ clause({clause,L,[{match,L,P0,All}],[],[All]}, StInt);
+ [_|_] ->
+ {C0,StInt} = clause({clause,L,[P0],[],[{nil,0}]}, St1),
+ {C0#iclause{body=Es1},StInt}
+ end,
+ {E1,Eps,St3} = novars(E0, St2),
+ {Fpat,St4} = new_var(St3),
+ Lanno = lineno_anno(L, St4),
+ Fc = #iclause{anno=#a{anno=[dialyzer_ignore,compiler_generated|Lanno]},pats=[Fpat],guard=[],
+ body=[#iapply{op=Fail,args=[Fpat]}]},
+ {Eps ++ [#icase{anno=#a{anno=Lanno},args=[E1],clauses=[C],fc=Fc}],St4};
+maybe_match_exprs([E0|Es0], Fail, St0) ->
+ {E1,Eps,St1} = expr(E0, St0),
+ {Es1,St2} = maybe_match_exprs(Es0, Fail, St1),
+ {Eps ++ [E1|Es1],St2};
+maybe_match_exprs([], _Fail, St) ->
+ {[],St}.
+
%% expr(Expr, State) -> {Cexpr,[PreExp],State}.
%% Generate an internal core expression.
@@ -668,6 +696,84 @@ expr({block,_,Es0}, St0) ->
{Es1,St1} = exprs(droplast(Es0), St0),
{E1,Eps,St2} = expr(last(Es0), St1),
{E1,Es1 ++ Eps,St2};
+expr({'maybe',L,Es}, St0) ->
+ {V,St1} = new_var_name(St0),
+ Var = {var,L,V},
+ Cs = [{clause,L,[Var],[],[Var]}],
+ expr({'maybe',L,Es,{'else',L,Cs}}, St1);
+expr({'maybe',L,Es0,{'else',_,Cs0}}, St0) ->
+ %% Translate the maybe ... else ... end construct.
+ %%
+ %% As an example, the following Erlang code:
+ %%
+ %% foo(A) ->
+ %% maybe
+ %% {ok, V} ?= A,
+ %% V
+ %% else
+ %% Other ->
+ %% {error, Other}
+ %% end.
+ %%
+ %% is translated into Core Erlang like this:
+ %%
+ %% 'foo'/1 =
+ %% fun (_0) ->
+ %% case _0 of
+ %% <A> when 'true' ->
+ %% ( letrec
+ %% 'maybe_else_fail'/1 =
+ %% fun (_3) ->
+ %% case _3 of
+ %% <_2> when 'true' ->
+ %% case _2 of
+ %% <Other> when 'true' ->
+ %% {'error',Other}
+ %% ( <_1> when 'true' ->
+ %% primop 'match_fail'({'else_clause',_1})
+ %% -| ['compiler_generated'] )
+ %% end
+ %% ( <_1> when 'true' ->
+ %% primop 'match_fail'('never_fails')
+ %% -| ['compiler_generated'] )
+ %% end
+ %% in
+ %% case A of
+ %% <{'ok',V}> when 'true' ->
+ %% V
+ %% ( <_4> when 'true' ->
+ %% apply 'maybe_else_fail'/1(_4)
+ %% -| ['dialyzer_ignore','compiler_generated'] )
+ %% end
+ %% -| ['letrec_goto','no_inline'] )
+ %% ( <_5> when 'true' ->
+ %% primop 'match_fail'({'function_clause',_5})
+ %% -| ['compiler_generated'] )
+ %% end
+ {[V1,V2,FailVar],St1} = new_vars(3, St0),
+
+ %% Translate the body of the letrec.
+ Fail = {maybe_else_fail,1},
+ Lanno = lineno_anno(L, St1),
+ {Es1,St2} = maybe_match_exprs(Es0, #c_var{name=Fail}, St1),
+
+ %% Translate the 'else' clauses. Note that we must not put the clauses
+ %% as the top-level clauses in the fun, because all shawdowing variables
+ %% in a fun head will be renamed.
+ {Cs1,St3} = clauses(Cs0, St2),
+ Fc1 = fail_clause([FailVar], Lanno, c_tuple([#c_literal{val=else_clause},FailVar])),
+ FailCase = #icase{args=[V2],clauses=Cs1,fc=Fc1},
+ FailFunCs = [#iclause{pats=[V2],guard=[#c_literal{val=true}],
+ body=[FailCase]}],
+ Anno = #a{anno=[letrec_goto,no_inline|Lanno]},
+ Fc2 = fail_clause([FailVar], Lanno, #c_literal{val=never_fails}),
+ FailFun = #ifun{id=[],vars=[V1],
+ clauses=FailFunCs,
+ fc=Fc2},
+
+ %% Construct the letrec.
+ Letrec = #iletrec{anno=Anno,defs=[{Fail,FailFun}],body=Es1},
+ {Letrec,[],St3};
expr({'if',L,Cs0}, St0) ->
{Cs1,St1} = clauses(Cs0, St0),
Lanno = lineno_anno(L, St1),
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 9783359fbb..6fd03d5732 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -38,6 +38,7 @@ MODULES= \
lc_SUITE \
map_SUITE \
match_SUITE \
+ maybe_SUITE \
misc_SUITE \
overridden_bif_SUITE \
random_code_SUITE \
@@ -72,6 +73,7 @@ NO_OPT= \
lc \
map \
match \
+ maybe \
misc \
overridden_bif \
receive \
@@ -98,6 +100,7 @@ INLINE= \
lc \
map \
match \
+ maybe \
misc \
overridden_bif \
receive \
@@ -162,8 +165,9 @@ RELSYSDIR = $(RELEASE_PATH)/compiler_test
# FLAGS
# ----------------------------------------------------
+MAYBE_OPT = '+{enable_feature,maybe_expr}'
ERL_MAKE_FLAGS +=
-ERL_COMPILE_FLAGS += +clint +clint0 +ssalint
+ERL_COMPILE_FLAGS += +clint +clint0 +ssalint $(MAYBE_OPT)
EBIN = .
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index f289209ac1..9e2f7ab1a1 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -180,7 +180,7 @@ recv(_Config) ->
self() ! 1,
{1,yes} = tricky_recv_2(),
self() ! 2,
- {2,maybe} = tricky_recv_2(),
+ {2,'maybe'} = tricky_recv_2(),
%% Test 'receive after infinity' in try/catch.
Pid = spawn(fun recv_after_inf_in_try/0),
@@ -284,7 +284,7 @@ tricky_recv_2() ->
end,
a;
X=2 ->
- Y = maybe,
+ Y = 'maybe',
b
end,
{X,Y}.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index b2ae96d55d..efb48b1ff4 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -273,10 +273,10 @@ booleans(_Config) ->
true = is_atom(AnyAtom),
false = is_boolean(AnyAtom),
- MaybeBool = id(maybe),
+ MaybeBool = id('maybe'),
case MaybeBool of
true -> ok;
- maybe -> ok;
+ 'maybe' -> ok;
false -> ok
end,
false = is_boolean(MaybeBool),
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 9bb1a57c6f..4e20162c7b 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -1439,7 +1439,8 @@ warnings(_Config) ->
test_lib:p_run(fun do_warnings/1, Files).
do_warnings(F) ->
- {ok,_,_,Ws} = compile:file(F, [binary,bin_opt_info,recv_opt_info,return]),
+ Options = [{enable_feature,maybe_expr},binary,bin_opt_info,recv_opt_info,return],
+ {ok,_,_,Ws} = compile:file(F, Options),
do_warnings_1(Ws, F).
do_warnings_1([{"no_file",Ws}|_], F) ->
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 008212a991..5fc487e7a9 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -497,7 +497,7 @@ untuplify_2(V1, V2) ->
shortcut_boolean(Config) when is_list(Config) ->
false = shortcut_boolean_1([0]),
true = shortcut_boolean_1({42}),
- maybe = shortcut_boolean_1(self()),
+ 'maybe' = shortcut_boolean_1(self()),
{'EXIT',_} = (catch shortcut_boolean_1([a,b])),
{'EXIT',_} = (catch shortcut_boolean_1({a,b})),
ok.
@@ -511,7 +511,7 @@ shortcut_boolean_1(X) ->
end,
not V;
false ->
- maybe
+ 'maybe'
end,
id(Outer).
diff --git a/lib/compiler/test/maybe_SUITE.erl b/lib/compiler/test/maybe_SUITE.erl
new file mode 100644
index 0000000000..bb355059a7
--- /dev/null
+++ b/lib/compiler/test/maybe_SUITE.erl
@@ -0,0 +1,273 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2020. 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).
+-include_lib("common_test/include/ct.hrl").
+
+-export([all/0, groups/0, init_per_suite/1, end_per_suite/1]).
+-export([basic/1, nested/1]).
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,[parallel],
+ [basic,nested]}].
+
+init_per_suite(Config) ->
+ test_lib:recompile(?MODULE),
+ 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,
+
+ 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}.
+
+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.
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index 65143d053c..01d7bd5853 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -86,6 +86,7 @@ opt_opts(Mod) ->
lists:filter(fun
(debug_info) -> true;
(dialyzer) -> true;
+ ({enable_feature,_}) -> true;
(inline) -> true;
(no_bsm3) -> true;
(no_bsm_opt) -> true;
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 53f21fcbbe..8ab46fed09 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -43,7 +43,7 @@
underscore/1,no_warnings/1,
bit_syntax/1,inlining/1,tuple_calls/1,
recv_opt_info/1,opportunistic_warnings/1,
- inline_list_funcs/1]).
+ eep49/1,inline_list_funcs/1]).
init_per_testcase(_Case, Config) ->
Config.
@@ -66,7 +67,7 @@ groups() ->
redundant_boolean_clauses,
underscore,no_warnings,bit_syntax,inlining,
tuple_calls,recv_opt_info,opportunistic_warnings]},
- inline_list_funcs]}].
+ eep49,inline_list_funcs]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -1165,6 +1167,27 @@ opportunistic_warnings(Config) ->
ok.
+%% Test value-based error handling (EEP 49).
+eep49(Config) ->
+ Ts = [{basic,
+ <<"foo(X) ->
+ maybe
+ %% There should be no warning.
+ Always ?= X,
+ Always
+ end.
+ ">>,
+ [{enable_feature,maybe_expr}],
+ []},
+ {disabled,
+ <<"foo() -> maybe. %Atom maybe.
+ ">>,
+ [{disable_feature,maybe_expr}],
+ []}
+ ],
+ run(Config, Ts),
+ ok.
+
%% GH-6158: There would be a warning for a clause that could not match.
inline_list_funcs(Config) ->
Ts = [{basic,
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 47da4bda9d..5a720b00f3 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -406,6 +406,17 @@ expr({'try',Anno,Es0,Scs0,Ccs0,As0}, St0) ->
expr({'catch',Anno,E0}, St0) ->
{E,St1} = expr(E0, St0),
{{'catch',Anno,E},St1};
+expr({'maybe',MaybeAnno,Es0}, St0) ->
+ {Es,St1} = exprs(Es0, St0),
+ {{'maybe',MaybeAnno,Es},St1};
+expr({'maybe',MaybeAnno,Es0,{'else',ElseAnno,Cs0}}, St0) ->
+ {Es,St1} = exprs(Es0, St0),
+ {Cs,St2} = clauses(Cs0, St1),
+ {{'maybe',MaybeAnno,Es,{'else',ElseAnno,Cs}},St2};
+expr({maybe_match,Anno,P0,E0}, St0) ->
+ {E,St1} = expr(E0, St0),
+ {P,St2} = pattern(P0, St1),
+ {{maybe_match,Anno,P,E},St2};
expr({match,Anno,P0,E0}, St0) ->
{E,St1} = expr(E0, St0),
{P,St2} = pattern(P0, St1),
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index fb5cb33c9b..84ae2f6bf9 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2606,6 +2606,23 @@ expr({match,_Anno,P,E}, Vt, St0) ->
{Pvt,Pnew,St2} = pattern(P, vtupdate(Evt, Vt), St1),
St = reject_invalid_alias_expr(P, E, Vt, St2),
{vtupdate(Pnew, vtmerge(Evt, Pvt)),St};
+expr({maybe_match,Anno,P,E}, Vt, St0) ->
+ expr({match,Anno,P,E}, Vt, St0);
+expr({'maybe',Anno,Es}, Vt, St) ->
+ %% No variables are exported.
+ {Evt0, St1} = exprs(Es, Vt, St),
+ Evt1 = vtupdate(vtunsafe({'maybe',Anno}, Evt0, Vt), Vt),
+ Evt2 = vtmerge(Evt0, Evt1),
+ {Evt2,St1};
+expr({'maybe',MaybeAnno,Es,{'else',ElseAnno,Cs}}, Vt, St) ->
+ %% No variables are exported.
+ {Evt0, St1} = exprs(Es, Vt, St),
+ Evt1 = vtupdate(vtunsafe({'maybe',MaybeAnno}, Evt0, Vt), Vt),
+ {Cvt0, St2} = icrt_clauses(Cs, {'else',ElseAnno}, Evt1, St1),
+ Cvt1 = vtupdate(vtunsafe({'else',ElseAnno}, Cvt0, Vt), Vt),
+ Evt2 = vtmerge(Evt0, Evt1),
+ Cvt2 = vtmerge(Cvt0, Cvt1),
+ {vtmerge(Evt2, Cvt2),St2};
%% No comparison or boolean operators yet.
expr({op,_Anno,_Op,A}, Vt, St) ->
expr(A, Vt, St);
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index f562c6d50a..6878af8886 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -48,13 +48,15 @@ top_type top_types type typed_expr typed_attr_val
type_sig type_sigs type_guard type_guards fun_type binary_type
type_spec spec_fun typed_exprs typed_record_fields field_types field_type
map_pair_types map_pair_type
-bin_base_type bin_unit_type.
+bin_base_type bin_unit_type
+maybe_expr maybe_match_exprs maybe_match.
Terminals
char integer float atom string var
'(' ')' ',' '->' '{' '}' '[' ']' '|' '||' '<-' ';' ':' '#' '.'
'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when'
+'maybe' 'else'
'andalso' 'orelse'
'bnot' 'not'
'*' '/' 'div' 'rem' 'band' 'and'
@@ -63,6 +65,7 @@ char integer float atom string var
'==' '/=' '=<' '<' '>=' '>' '=:=' '=/=' '<=' '=>' ':='
'<<' '>>'
'!' '=' '::' '..' '...'
+'?='
'spec' 'callback' % helper
dot.
@@ -257,6 +260,7 @@ expr_max -> case_expr : '$1'.
expr_max -> receive_expr : '$1'.
expr_max -> fun_expr : '$1'.
expr_max -> try_expr : '$1'.
+expr_max -> maybe_expr : '$1'.
pat_expr -> pat_expr '=' pat_expr : {match,first_anno('$1'),'$1','$3'}.
pat_expr -> pat_expr comp_op pat_expr : ?mkop2('$1', '$2', '$3').
@@ -401,7 +405,6 @@ if_clauses -> if_clause ';' if_clauses : ['$1' | '$3'].
if_clause -> guard clause_body :
{clause,first_anno(hd(hd('$1'))),[],'$1','$2'}.
-
case_expr -> 'case' expr 'of' cr_clauses 'end' :
{'case',?anno('$1'),'$2','$4'}.
@@ -477,6 +480,21 @@ try_clause -> var ':' pat_expr try_opt_stacktrace clause_guard clause_body :
try_opt_stacktrace -> ':' var : '$2'.
try_opt_stacktrace -> '$empty' : '_'.
+
+maybe_expr -> 'maybe' maybe_match_exprs 'end' :
+ {'maybe',?anno('$1'),'$2'}.
+maybe_expr -> 'maybe' maybe_match_exprs 'else' cr_clauses 'end' :
+ %% `erl_lint` can produce a better warning when the position
+ %% of the `else` keyword is known.
+ {'maybe',?anno('$1'),'$2',{'else',?anno('$3'),'$4'}}.
+
+maybe_match_exprs -> maybe_match : ['$1'].
+maybe_match_exprs -> maybe_match ',' maybe_match_exprs : ['$1' | '$3'].
+maybe_match_exprs -> expr : ['$1'].
+maybe_match_exprs -> expr ',' maybe_match_exprs : ['$1' | '$3'].
+
+maybe_match -> expr '?=' expr : {maybe_match,?anno('$2'),'$1','$3'}.
+
argument_list -> '(' ')' : {[],?anno('$1')}.
argument_list -> '(' exprs ')' : {'$2',?anno('$1')}.
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 5a537e092d..191aa75698 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -704,6 +704,14 @@ lexpr({'catch',_,Expr}, Prec, Opts) ->
{P,R} = preop_prec('catch'),
El = {list,[{step,'catch',lexpr(Expr, R, Opts)}]},
maybe_paren(P, Prec, El);
+lexpr({'maybe',_,Es}, _, Opts) ->
+ {list,[{step,'maybe',body(Es, Opts)},{reserved,'end'}]};
+lexpr({'maybe',_,Es,{'else',_,Cs}}, _, Opts) ->
+ {list,[{step,'maybe',body(Es, Opts)},{step,'else',cr_clauses(Cs, Opts)},{reserved,'end'}]};
+lexpr({maybe_match,_,Lhs,Rhs}, _, Opts) ->
+ Pl = lexpr(Lhs, 0, Opts),
+ Rl = lexpr(Rhs, 0, Opts),
+ {list,[{cstep,[Pl,leaf(" ?=")],Rl}]};
lexpr({match,_,Lhs,Rhs}, Prec, Opts) ->
{L,P,R} = inop_prec('='),
Pl = lexpr(Lhs, L, Opts),
@@ -1359,7 +1367,7 @@ wordtable() ->
L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end ||
W <- [" ->"," =","<<",">>","[]","after","begin","case","catch",
"end","fun","if","of","receive","try","when"," ::","..",
- " |"]],
+ " |","maybe","else"]],
list_to_tuple(L).
word(' ->', WT) -> element(1, WT);
@@ -1380,7 +1388,9 @@ word('try', WT) -> element(15, WT);
word('when', WT) -> element(16, WT);
word(' ::', WT) -> element(17, WT);
word('..', WT) -> element(18, WT);
-word(' |', WT) -> element(19, WT).
+word(' |', WT) -> element(19, WT);
+word('maybe', WT) -> element(20, WT);
+word('else', WT) -> element(21, WT).
%% Make up an unique variable name for Name that won't clash with any
%% name in Used. We first try by converting the name to uppercase and
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
index 5d988e7438..d1c3b94cf3 100644
--- a/lib/stdlib/src/erl_scan.erl
+++ b/lib/stdlib/src/erl_scan.erl
@@ -427,6 +427,11 @@ scan1([C|Cs], St, Line, Col, Toks) when ?WHITE_SPACE(C) ->
skip_white_space(Cs, St, Line, Col, Toks, 1)
end;
%% Punctuation characters and operators, first recognise multiples.
+%% ?= for the maybe ... else ... end construct
+scan1("?="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "?=", '?=', 2);
+scan1("?"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
%% << <- <=
scan1("<<"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "<<", '<<', 2);
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index 7f2f7b1c28..9a4b430c88 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -2010,6 +2010,9 @@ eval_tests(Config, Fun, Tests) ->
F = fun({N,P,Opts,E}, BadL) ->
%% io:format("Testing ~p~n", [P]),
Return = Fun(Config, P, Opts),
+ %% The result should be the same when enabling maybe ... end
+ %% (making 'else' a keyword instead of an atom).
+ Return = Fun(Config, P, [{enable_feature,maybe_expr}|Opts]),
case message_compare(E, Return) of
true ->
case E of
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 7e89b5f891..ed7b694e8f 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -77,7 +77,8 @@
otp_16824/1,
underscore_match/1,
unused_record/1,
- unused_type2/1]).
+ unused_type2/1,
+ eep49/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -103,7 +104,8 @@ all() ->
stacktrace_syntax, otp_14285, otp_14378, external_funs,
otp_15456, otp_15563, unused_type, binary_types, removed, otp_16516,
inline_nifs, warn_missing_spec, otp_16824,
- underscore_match, unused_record, unused_type2].
+ underscore_match, unused_record, unused_type2,
+ eep49].
groups() ->
[{unused_vars_warn, [],
@@ -4723,6 +4725,84 @@ unused_type2(Config) when is_list(Config) ->
ok.
+%% Test maybe ... else ... end.
+eep49(Config) when is_list(Config) ->
+ EnableMaybe = {enable_feature,maybe_expr},
+ Ts = [{exp1,
+ <<"t(X) ->
+ maybe
+ A = X()
+ end,
+ A.
+ ">>,
+ [EnableMaybe],
+ {errors,[{{5,19},erl_lint,{unsafe_var,'A',{'maybe',{2,19}}}}],
+ []}},
+
+ {exp2,
+ <<"t(X) ->
+ maybe
+ A = X()
+ else
+ _ -> {ok,A}
+ end,
+ A.
+ ">>,
+ [EnableMaybe],
+ {errors,[{{5,32},erl_lint,{unsafe_var,'A',{'maybe',{2,19}}}},
+ {{7,19},erl_lint,{unsafe_var,'A',{'maybe',{2,19}}}}],
+ []}},
+
+ {exp3,
+ <<"t(X) ->
+ maybe
+ X()
+ else
+ A ->
+ B = 42,
+ {error,A}
+ end,
+ {A,B}.
+ ">>,
+ [EnableMaybe],
+ {errors,[{{9,20},erl_lint,{unsafe_var,'A',{'else',{4,19}}}},
+ {{9,22},erl_lint,{unsafe_var,'B',{'else',{4,19}}}}],
+ []}},
+
+ {exp4,
+ <<"t(X) ->
+ maybe
+ X()
+ else
+ ok ->
+ A = 42;
+ error ->
+ error
+ end,
+ A.
+ ">>,
+ [EnableMaybe],
+ {errors,[{{10,19},erl_lint,{unsafe_var,'A',{'else',{4,19}}}}],
+ []}},
+
+ %% Using '?=' not at the top-level of a 'maybe' ... 'else' is forbidden.
+ {illegal_maybe_match1,
+ <<"t(X) ->
+ maybe (ok ?= X()) end.
+ ">>,
+ [EnableMaybe],
+ {errors,[{{2,29},erl_parse,["syntax error before: ","'?='"]}],[]}},
+ {illegal_maybe_match2,
+ <<"t(X) ->
+ ok ?= X().
+ ">>,
+ [EnableMaybe],
+ {errors,[{{2,22},erl_parse,["syntax error before: ","'?='"]}],[]}}
+ ],
+
+ [] = run(Config, Ts),
+ ok.
+
format_error(E) ->
lists:flatten(erl_lint:format_error(E)).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index f1e3544337..c8c1a206ca 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -55,7 +55,8 @@
otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1, otp_9147/1,
otp_10302/1, otp_10820/1, otp_11100/1, otp_11861/1, pr_1014/1,
otp_13662/1, otp_14285/1, otp_15592/1, otp_15751/1, otp_15755/1,
- otp_16435/1, gh_5093/1]).
+ otp_16435/1, gh_5093/1,
+ eep49/1]).
%% Internal export.
-export([ehook/6]).
@@ -87,7 +88,7 @@ groups() ->
otp_8473, otp_8522, otp_8567, otp_8664, otp_9147,
otp_10302, otp_10820, otp_11100, otp_11861, pr_1014, otp_13662,
otp_14285, otp_15592, otp_15751, otp_15755, otp_16435,
- gh_5093]}].
+ gh_5093, eep49]}].
init_per_suite(Config) ->
Config.
@@ -1380,6 +1381,18 @@ gh_5093(_Config) ->
assert_same("f(X, Y) ->\n X - Y.\n"),
ok.
+eep49(_Config) ->
+ assert_same("f() ->\n"
+ " maybe ok ?= ok end.\n"),
+ assert_same("f() ->\n"
+ " maybe\n"
+ " ok ?= ok\n"
+ " else\n"
+ " {error, _} ->\n"
+ " error\n"
+ " end.\n"),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile(Config, Tests) ->
@@ -1475,7 +1488,15 @@ parse_forms(Chars) ->
parse_forms2([], _Cont, _Line, Forms) ->
lists:reverse(Forms);
parse_forms2(String, Cont0, Line, Forms) ->
- case erl_scan:tokens(Cont0, String, Line) of
+ %% FIXME: When the experimental features EEP has been implemented, we should
+ %% dig out all keywords defined in all features.
+ ResWordFun =
+ fun('maybe') -> true;
+ ('else') -> true;
+ (Other) -> erl_scan:reserved_word(Other)
+ end,
+ Options = [{reserved_word_fun,ResWordFun}],
+ case erl_scan:tokens(Cont0, String, Line, Options) of
{done, {ok, Tokens, EndLine}, Chars} ->
{ok, Form} = erl_parse:parse_form(Tokens),
parse_forms2(Chars, [], EndLine, [Form | Forms]);
--
2.34.1