Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
3505-Implement-the-compiler-part-of-EEP-49.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor