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

openSUSE Build Service is sponsored by