diff options
author | Björn Gustavsson <bjorn@erlang.org> | 2019-11-29 11:29:32 +0100 |
---|---|---|
committer | Björn Gustavsson <bjorn@erlang.org> | 2020-02-06 12:27:30 +0100 |
commit | c9a1ae2892a41d053851fb6b7d73adb87bf19510 (patch) | |
tree | 999cb598a1364c6ed45a7da2f7190b44398da1c1 | |
parent | 1b23945dd7b3f0c8e88692ddfe41de446230ac5d (diff) | |
download | erlang-c9a1ae2892a41d053851fb6b7d73adb87bf19510.tar.gz |
Allow guard expressions in map keys
-rw-r--r-- | lib/compiler/src/v3_core.erl | 340 | ||||
-rw-r--r-- | lib/compiler/test/error_SUITE.erl | 3 | ||||
-rw-r--r-- | lib/compiler/test/map_SUITE.erl | 63 | ||||
-rw-r--r-- | lib/stdlib/src/erl_lint.erl | 82 | ||||
-rw-r--r-- | lib/stdlib/test/erl_lint_SUITE.erl | 2 |
5 files changed, 245 insertions, 245 deletions
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 0ce04b1b35..7653f75647 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -105,6 +105,8 @@ -record(ifun, {anno=#a{},id,vars,clauses,fc,name=unnamed}). -record(iletrec, {anno=#a{},defs,body}). -record(imatch, {anno=#a{},pat,guard=[],arg,fc}). +-record(imap, {anno=#a{},arg=#c_literal{val=#{}},es,is_pat=false}). +-record(imappair, {anno=#a{},op,key,val}). -record(iprimop, {anno=#a{},name,args}). -record(iprotect, {anno=#a{},body}). -record(ireceive1, {anno=#a{},clauses}). @@ -112,7 +114,7 @@ -record(iset, {anno=#a{},var,arg}). -record(itry, {anno=#a{},args,vars,body,evars,handler}). -record(ifilter, {anno=#a{},arg}). --record(igen, {anno=#a{},ceps=[],acc_pat,acc_guard, +-record(igen, {anno=#a{},acc_pat,acc_guard, skip_pat,tail,tail_pat,arg}). -record(isimple, {anno=#a{},term :: cerl:cerl()}). @@ -125,6 +127,7 @@ -type ifun() :: #ifun{}. -type iletrec() :: #iletrec{}. -type imatch() :: #imatch{}. +-type imap() :: #imap{}. -type iprimop() :: #iprimop{}. -type iprotect() :: #iprotect{}. -type ireceive1() :: #ireceive1{}. @@ -135,10 +138,11 @@ -type igen() :: #igen{}. -type isimple() :: #isimple{}. --type i() :: iapply() | ibinary() | icall() | icase() | icatch() - | iclause() | ifun() | iletrec() | imatch() | iprimop() - | iprotect() | ireceive1() | ireceive2() | iset() | itry() - | ifilter() | igen() | isimple(). +-type i() :: iapply() | ibinary() | icall() | icase() | icatch() + | iclause() | ifun() | iletrec() | imatch() | imap() + | iprimop() | iprotect() | ireceive1() | ireceive2() + | iset() | itry() | ifilter() + | igen() | isimple(). -type warning() :: {file:filename(), [{integer(), module(), term()}]}. @@ -249,55 +253,37 @@ body(Cs0, Name, Arity, St0) -> FunAnno = [{function,{Name,Arity}} | Anno], {Args0,St1} = new_vars(Anno, Arity, St0), Args = reverse(Args0), %Nicer order - case clauses(Cs0, St1) of - {Cs1,[],St2} -> - {Ps,St3} = new_vars(Arity, St2), %Need new variables here - Fc = function_clause(Ps, Anno), - {#ifun{anno=#a{anno=FunAnno},id=[],vars=Args,clauses=Cs1,fc=Fc},St3}; - {Cs1,Eps,St2} -> - %% We have pre-expressions from patterns and - %% these needs to be letified before matching - %% since only bound variables are allowed - AnnoGen = #a{anno=[compiler_generated]}, - {Ps1,St3} = new_vars(Arity, St2), %Need new variables here - Fc1 = function_clause(Ps1, Anno), - {Ps2,St4} = new_vars(Arity, St3), %Need new variables here - Fc2 = function_clause(Ps2, Anno), - Case = #icase{anno=AnnoGen,args=Args, - clauses=Cs1, - fc=Fc2}, - {#ifun{anno=#a{anno=FunAnno},id=[],vars=Args, - clauses=[#iclause{anno=AnnoGen,pats=Ps1, - guard=[#c_literal{val=true}], - body=Eps ++ [Case]}], - fc=Fc1},St4} - end. + {Cs1,St2} = clauses(Cs0, St1), + {Ps,St3} = new_vars(Arity, St2), %Need new variables here + Fc = function_clause(Ps, Anno), + {#ifun{anno=#a{anno=FunAnno},id=[],vars=Args,clauses=Cs1,fc=Fc},St3}. %% clause(Clause, State) -> {Cclause,State} | noclause. %% clauses([Clause], State) -> {[Cclause],State}. %% Convert clauses. Trap bad pattern aliases and remove clause from %% clause list. -clauses([C0|Cs0],St0) -> +clauses([C0|Cs0], St0) -> case clause(C0, St0) of - {noclause,_,St} -> clauses(Cs0,St); - {C,Eps1,St1} -> - {Cs,Eps2,St2} = clauses(Cs0, St1), - {[C|Cs],Eps1++Eps2,St2} + {noclause,St} -> + clauses(Cs0, St); + {C,St1} -> + {Cs,St2} = clauses(Cs0, St1), + {[C|Cs],St2} end; -clauses([],St) -> {[],[],St}. +clauses([], St) -> {[],St}. clause({clause,Lc,H0,G0,B0}, St0) -> try head(H0, St0) of - {H1,Eps,St1} -> + {H1,St1} -> {G1,St2} = guard(G0, St1), {B1,St3} = exprs(B0, St2), Anno = lineno_anno(Lc, St3), - {#iclause{anno=#a{anno=Anno},pats=H1,guard=G1,body=B1},Eps,St3} + {#iclause{anno=#a{anno=Anno},pats=H1,guard=G1,body=B1},St3} catch throw:nomatch -> St = add_warning(Lc, nomatch, St0), - {noclause,[],St} %Bad pattern + {noclause,St} %Bad pattern end. clause_arity({clause,_,H0,_,_}) -> length(H0). @@ -632,26 +618,26 @@ expr({block,_,Es0}, St0) -> {E1,Eps,St2} = expr(last(Es0), St1), {E1,Es1 ++ Eps,St2}; expr({'if',L,Cs0}, St0) -> - {Cs1,Ceps,St1} = clauses(Cs0, St0), + {Cs1,St1} = clauses(Cs0, St0), Lanno = lineno_anno(L, St1), Fc = fail_clause([], Lanno, #c_literal{val=if_clause}), - {#icase{anno=#a{anno=Lanno},args=[],clauses=Cs1,fc=Fc},Ceps,St1}; + {#icase{anno=#a{anno=Lanno},args=[],clauses=Cs1,fc=Fc},[],St1}; expr({'case',L,E0,Cs0}, St0) -> {E1,Eps,St1} = novars(E0, St0), - {Cs1,Ceps,St2} = clauses(Cs0, St1), + {Cs1,St2} = clauses(Cs0, St1), {Fpat,St3} = new_var(St2), Lanno = lineno_anno(L, St2), Fc = fail_clause([Fpat], Lanno, c_tuple([#c_literal{val=case_clause},Fpat])), - {#icase{anno=#a{anno=Lanno},args=[E1],clauses=Cs1,fc=Fc},Eps++Ceps,St3}; + {#icase{anno=#a{anno=Lanno},args=[E1],clauses=Cs1,fc=Fc},Eps,St3}; expr({'receive',L,Cs0}, St0) -> - {Cs1,Ceps,St1} = clauses(Cs0, St0), - {#ireceive1{anno=#a{anno=lineno_anno(L, St1)},clauses=Cs1},Ceps, St1}; + {Cs1,St1} = clauses(Cs0, St0), + {#ireceive1{anno=#a{anno=lineno_anno(L, St1)},clauses=Cs1},[],St1}; expr({'receive',L,Cs0,Te0,Tes0}, St0) -> {Te1,Teps,St1} = novars(Te0, St0), {Tes1,St2} = exprs(Tes0, St1), - {Cs1,Ceps,St3} = clauses(Cs0, St2), + {Cs1,St3} = clauses(Cs0, St2), {#ireceive2{anno=#a{anno=lineno_anno(L, St3)}, - clauses=Cs1,timeout=Te1,action=Tes1},Teps++Ceps,St3}; + clauses=Cs1,timeout=Te1,action=Tes1},Teps,St3}; expr({'try',L,Es0,[],Ecs,[]}, St0) -> %% 'try ... catch ... end' {Es1,St1} = exprs(Es0, St0), @@ -665,7 +651,7 @@ expr({'try',L,Es0,Cs0,Ecs,[]}, St0) -> %% 'try ... of ... catch ... end' {Es1,St1} = exprs(Es0, St0), {V,St2} = new_var(St1), %This name should be arbitrary - {Cs1,Ceps,St3} = clauses(Cs0, St2), + {Cs1,St3} = clauses(Cs0, St2), {Fpat,St4} = new_var(St3), Lanno = lineno_anno(L, St4), Fc = fail_clause([Fpat], Lanno, @@ -674,7 +660,7 @@ expr({'try',L,Es0,Cs0,Ecs,[]}, St0) -> {#itry{anno=#a{anno=lineno_anno(L, St5)},args=Es1, vars=[V],body=[#icase{anno=#a{anno=Lanno},args=[V],clauses=Cs1,fc=Fc}], evars=Evs,handler=Hs}, - Ceps,St5}; + [],St5}; expr({'try',L,Es0,[],[],As0}, St0) -> %% 'try ... after ... end' {Es1,St1} = exprs(Es0, St0), @@ -743,12 +729,12 @@ expr({match,L,P0,E0}, St0) -> end, {E2,Eps1,St2} = novars(E1, St1), St3 = St2#core{wanted=St0#core.wanted}, - {P2,Eps2,St4} = try - pattern(P1, St3) - catch - throw:Thrown -> - {Thrown,[],St3} - end, + {P2,St4} = try + pattern(P1, St3) + catch + throw:Thrown -> + {Thrown,St3} + end, {Fpat,St5} = new_var(St4), Lanno = lineno_anno(L, St5), Fc = fail_clause([Fpat], Lanno, c_tuple([#c_literal{val=badmatch},Fpat])), @@ -777,15 +763,15 @@ expr({match,L,P0,E0}, St0) -> St6 = add_warning(L, nomatch, St5), {Expr,Eps3,St7} = safe(E1, St6), SanPat0 = sanitize(P1), - {SanPat,Eps4,St} = pattern(SanPat0, St7), + {SanPat,St} = pattern(SanPat0, St7), Badmatch = c_tuple([#c_literal{val=badmatch},Expr]), Fail = #iprimop{anno=#a{anno=Lanno}, name=#c_literal{val=match_fail}, args=[Badmatch]}, - Eps = Eps3 ++ Eps4 ++ [Fail], + Eps = Eps3 ++ [Fail], {#imatch{anno=#a{anno=Lanno},pat=SanPat,arg=Expr,fc=Fc},Eps,St}; Other when not is_atom(Other) -> - {#imatch{anno=#a{anno=Lanno},pat=P2,arg=E2,fc=Fc},Eps1++Eps2,St5} + {#imatch{anno=#a{anno=Lanno},pat=P2,arg=E2,fc=Fc},Eps1,St5} end; expr({op,_,'++',{lc,Llc,E,Qs0},More}, St0) -> %% Optimise '++' here because of the list comprehension algorithm. @@ -957,7 +943,7 @@ is_valid_map_src(_) -> false. try_exception(Ecs0, St0) -> %% Note that Tag is not needed for rethrow - it is already in Info. {Evs,St1} = new_vars(3, St0), % Tag, Value, Info - {Ecs1,Ceps,St2} = clauses(Ecs0, St1), + {Ecs1,St2} = clauses(Ecs0, St1), Ecs2 = try_build_stacktrace(Ecs1, hd(Evs)), [_,Value,Info] = Evs, LA = case Ecs2 of @@ -970,7 +956,7 @@ try_exception(Ecs0, St0) -> name=#c_literal{val=raise}, args=[Info,Value]}]}, Hs = [#icase{anno=#a{anno=LA},args=[c_tuple(Evs)],clauses=Ecs2,fc=Ec}], - {Evs,Ceps++Hs,St2}. + {Evs,Hs,St2}. try_after(As, St0) -> %% See above. @@ -1191,7 +1177,7 @@ bitstr({bin_element,_,E0,Size0,[Type,{unit,Unit}|Flags]}, St0) -> fun_tq(Cs0, L, St0, NameInfo) -> Arity = clause_arity(hd(Cs0)), - {Cs1,Ceps,St1} = clauses(Cs0, St0), + {Cs1,St1} = clauses(Cs0, St0), {Args,St2} = new_vars(Arity, St1), {Ps,St3} = new_vars(Arity, St2), %Need new variables here Anno = full_anno(L, St3), @@ -1201,12 +1187,12 @@ fun_tq(Cs0, L, St0, NameInfo) -> Fun = #ifun{anno=#a{anno=Anno}, id=[{id,Id}], %We KNOW! vars=Args,clauses=Cs1,fc=Fc,name=NameInfo}, - {Fun,Ceps,St4}. + {Fun,[],St4}. %% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}. %% This TQ from Simon PJ pp 127-138. -lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,ceps=Ceps, +lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno, acc_pat=AccPat,acc_guard=AccGuard, skip_pat=SkipPat,tail=Tail,tail_pat=TailPat, arg={Pre,Arg}}|Qs], Mc, St0) -> @@ -1242,7 +1228,7 @@ lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,ceps=Ceps, Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc}, {#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}], body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]}, - Ceps,St4}; + [],St4}; lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) -> filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5); lc_tq(Line, E0, [], Mc0, St0) -> @@ -1267,7 +1253,7 @@ bc_tq(Line, Exp, Qs0, St0) -> args=[Sz]}}] ++ BcPre, {E,Pre,St}. -bc_tq1(Line, E, [#igen{anno=GAnno,ceps=Ceps, +bc_tq1(Line, E, [#igen{anno=GAnno, acc_pat=AccPat,acc_guard=AccGuard, skip_pat=SkipPat,tail=Tail,tail_pat=TailPat, arg={Pre,Arg}}|Qs], Mc, St0) -> @@ -1306,7 +1292,7 @@ bc_tq1(Line, E, [#igen{anno=GAnno,ceps=Ceps, Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc}, {#iletrec{anno=LAnno#a{anno=[list_comprehension|LA]},defs=[{{Name,2},Fun}], body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]}, - Ceps,St4}; + [],St4}; bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) -> filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5); bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) -> @@ -1448,7 +1434,7 @@ get_qual_anno(Abstract) -> element(2, Abstract). generator(Line, {generate,Lg,P0,E}, Gs, St0) -> LA = lineno_anno(Line, St0), GA = lineno_anno(Lg, St0), - {Head,Ceps,St1} = list_gen_pattern(P0, Line, St0), + {Head,St1} = list_gen_pattern(P0, Line, St0), {[Tail,Skip],St2} = new_vars(2, St1), {Cg,St3} = lc_guard_tests(Gs, St2), {AccPat,SkipPat} = case Head of @@ -1468,7 +1454,7 @@ generator(Line, {generate,Lg,P0,E}, Gs, St0) -> ann_c_cons(LA, Skip, Tail)} end, {Ce,Pre,St4} = safe(E, St3), - Gen = #igen{anno=#a{anno=GA},ceps=Ceps, + Gen = #igen{anno=#a{anno=GA}, acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat, tail=Tail,tail_pat=#c_literal{anno=LA,val=[]},arg={Pre,Ce}}, {Gen,St4}; @@ -1476,7 +1462,7 @@ generator(Line, {b_generate,Lg,P,E}, Gs, St0) -> LA = lineno_anno(Line, St0), GA = lineno_anno(Lg, St0), try pattern(P, St0) of - {#ibinary{segments=Segs}=Cp,[],St1} -> + {#ibinary{segments=Segs}=Cp,St1} -> %% The function append_tail_segment/2 keeps variable %% patterns as-is, making it possible to have the same %% skip clause removal as with list generators. @@ -1528,9 +1514,9 @@ lc_guard_tests(Gs0, St0) -> list_gen_pattern(P0, Line, St) -> try - pattern(P0,St) - catch - nomatch -> {nomatch,[],add_warning(Line, nomatch, St)} + pattern(P0, St) + catch + nomatch -> {nomatch,add_warning(Line, nomatch, St)} end. %%% @@ -1799,18 +1785,6 @@ force_novars(#c_map{}=Bin, St) -> {Bin,[],St}; force_novars(Ce, St) -> force_safe(Ce, St). - -%% safe_pattern_expr(Expr, State) -> {Cexpr,[PreExpr],State}. -%% only literals and variables are safe expressions in patterns -safe_pattern_expr(E,St0) -> - case safe(E,St0) of - {#c_var{},_,_}=Safe -> Safe; - {#c_literal{},_,_}=Safe -> Safe; - {Ce,Eps,St1} -> - {V,St2} = new_var(St1), - {V,Eps++[#iset{var=V,arg=Ce}],St2} - end. - %% safe(Expr, State) -> {Safe,[PreExpr],State}. %% Generate an internal safe expression. These are simples without %% binaries which can fail. At this level we do not need to do a @@ -1878,43 +1852,33 @@ fold_match({match,L,P0,E0}, P) -> fold_match(E, P) -> {P,E}. %% pattern(Pattern, State) -> {CorePat,[PreExp],State}. -%% Transform a pattern by removing line numbers. We also normalise -%% aliases in patterns to standard form, {alias,Pat,[Var]}. -%% -%% In patterns we may have expressions -%% 1) Binaries -> #c_bitstr{size=Expr} -%% 2) Maps -> #c_map_pair{key=Expr} -%% -%% Both of these may generate pre-expressions since only bound variables -%% or literals are allowed for these in core patterns. -%% -%% Therefor, we need to drag both the state and the collection of pre-expression -%% around in the whole pattern transformation tree. - -pattern({var,L,V}, St) -> {#c_var{anno=lineno_anno(L, St),name=V},[],St}; -pattern({char,L,C}, St) -> {#c_literal{anno=lineno_anno(L, St),val=C},[],St}; -pattern({integer,L,I}, St) -> {#c_literal{anno=lineno_anno(L, St),val=I},[],St}; -pattern({float,L,F}, St) -> {#c_literal{anno=lineno_anno(L, St),val=F},[],St}; -pattern({atom,L,A}, St) -> {#c_literal{anno=lineno_anno(L, St),val=A},[],St}; -pattern({string,L,S}, St) -> {#c_literal{anno=lineno_anno(L, St),val=S},[],St}; -pattern({nil,L}, St) -> {#c_literal{anno=lineno_anno(L, St),val=[]},[],St}; +%% Transform a pattern by removing line numbers. We also normalise +%% aliases in patterns to standard form: {alias,Pat,[Var]}. + +pattern({var,L,V}, St) -> {#c_var{anno=lineno_anno(L, St),name=V},St}; +pattern({char,L,C}, St) -> {#c_literal{anno=lineno_anno(L, St),val=C},St}; +pattern({integer,L,I}, St) -> {#c_literal{anno=lineno_anno(L, St),val=I},St}; +pattern({float,L,F}, St) -> {#c_literal{anno=lineno_anno(L, St),val=F},St}; +pattern({atom,L,A}, St) -> {#c_literal{anno=lineno_anno(L, St),val=A},St}; +pattern({string,L,S}, St) -> {#c_literal{anno=lineno_anno(L, St),val=S},St}; +pattern({nil,L}, St) -> {#c_literal{anno=lineno_anno(L, St),val=[]},St}; pattern({cons,L,H,T}, St) -> - {Ph,Eps1,St1} = pattern(H, St), - {Pt,Eps2,St2} = pattern(T, St1), - {annotate_cons(lineno_anno(L, St), Ph, Pt, St2),Eps1++Eps2,St2}; + {Ph,St1} = pattern(H, St), + {Pt,St2} = pattern(T, St1), + {annotate_cons(lineno_anno(L, St), Ph, Pt, St2),St2}; pattern({tuple,L,Ps}, St) -> - {Ps1,Eps,St1} = pattern_list(Ps,St), - {annotate_tuple(record_anno(L, St), Ps1, St),Eps,St1}; + {Ps1,St1} = pattern_list(Ps, St), + {annotate_tuple(record_anno(L, St), Ps1, St),St1}; pattern({map,L,Pairs}, St0) -> - {Ps,Eps,St1} = pattern_map_pairs(Pairs, St0), - {#c_map{anno=lineno_anno(L, St1),es=Ps,is_pat=true},Eps,St1}; + {Ps,St1} = pattern_map_pairs(Pairs, St0), + {#imap{anno=#a{anno=lineno_anno(L, St1)},es=Ps},St1}; pattern({bin,L,Ps}, St0) -> {Segments,St} = pat_bin(Ps, St0), - {#ibinary{anno=#a{anno=lineno_anno(L, St)},segments=Segments},[],St}; + {#ibinary{anno=#a{anno=lineno_anno(L, St)},segments=Segments},St}; pattern({match,_,P1,P2}, St) -> - {Cp1,Eps1,St1} = pattern(P1,St), - {Cp2,Eps2,St2} = pattern(P2,St1), - {pat_alias(Cp1,Cp2),Eps1++Eps2,St2}; + {Cp1,St1} = pattern(P1, St), + {Cp2,St2} = pattern(P2, St1), + {pat_alias(Cp1, Cp2),St2}; %% Evaluate compile-time expressions. pattern({op,_,'++',{nil,_},R}, St) -> pattern(R, St); @@ -1928,30 +1892,22 @@ pattern({op,_Line,_Op,_L,_R}=Op, St) -> pattern(erl_eval:partial_eval(Op), St). %% pattern_map_pairs([MapFieldExact],State) -> [#c_map_pairs{}] -pattern_map_pairs(Ps, St) -> - %% check literal key uniqueness - %% - guaranteed via aliasing map pairs - %% pattern all pairs in two steps - %% 1) Construct Core Pattern - %% 2) Alias Keys in Core Pattern - {CMapPairs, {Eps,St1}} = lists:mapfoldl(fun - (P,{EpsM,Sti0}) -> - {CMapPair,EpsP,Sti1} = pattern_map_pair(P,Sti0), - {CMapPair, {EpsM++EpsP,Sti1}} - end, {[],St}, Ps), - {pat_alias_map_pairs(CMapPairs),Eps,St1}. +pattern_map_pairs(Ps, St0) -> + {CMapPairs,St1} = mapfoldl(fun pattern_map_pair/2, St0, Ps), + {pat_alias_map_pairs(CMapPairs),St1}. pattern_map_pair({map_field_exact,L,K,V}, St0) -> - {Ck,EpsK,St1} = safe_pattern_expr(K, St0), - {Cv,EpsV,St2} = pattern(V, St1), - {#c_map_pair{anno=lineno_anno(L, St2), - op=#c_literal{val=exact}, - key=Ck, - val=Cv},EpsK++EpsV,St2}. + Ck0 = erl_eval:partial_eval(K), + {Ck,St1} = exprs([Ck0], St0), + {Cv,St2} = pattern(V, St1), + {#imappair{anno=#a{anno=lineno_anno(L, St2)}, + op=#c_literal{val=exact}, + key=Ck, + val=Cv},St2}. pat_alias_map_pairs(Ps) -> - D0 = foldl(fun(#c_map_pair{key=K0}=Pair, A) -> - K = cerl:set_ann(K0, []), + D0 = foldl(fun(#imappair{key=K0}=Pair, A) -> + K = map_sort_key(K0, A), case A of #{K:=Aliases} -> A#{K:=[Pair|Aliases]}; @@ -1964,13 +1920,23 @@ pat_alias_map_pairs(Ps) -> D = sort(maps:to_list(D0)), pat_alias_map_pairs_1(D). -pat_alias_map_pairs_1([{_,[#c_map_pair{val=V0}=Pair|Vs]}|T]) -> - V = foldl(fun(#c_map_pair{val=V}, Pat) -> +pat_alias_map_pairs_1([{_,[#imappair{val=V0}=Pair|Vs]}|T]) -> + V = foldl(fun(#imappair{val=V}, Pat) -> pat_alias(V, Pat) end, V0, Vs), - [Pair#c_map_pair{val=V}|pat_alias_map_pairs_1(T)]; + [Pair#imappair{val=V}|pat_alias_map_pairs_1(T)]; pat_alias_map_pairs_1([]) -> []. +map_sort_key(Key, KeyMap) -> + case Key of + [#c_literal{}=Lit] -> + {atomic,cerl:set_ann(Lit, [])}; + [#c_var{}=Var] -> + {atomic,cerl:set_ann(Var, [])}; + _ -> + {expr,map_size(KeyMap)} + end. + %% pat_bin([BinElement], State) -> [BinSeg]. pat_bin(Ps0, St) -> @@ -1987,7 +1953,7 @@ pat_segment({bin_element,L,Val,Size0,Type0}, St) -> {Size1,Type1} = make_bit_type(L, Size0, Type0), [Type,{unit,Unit}|Flags] = Type1, Anno = lineno_anno(L, St), - {Pval0,[],St1} = pattern(Val, St), + {Pval0,St1} = pattern(Val, St), Pval = coerce_to_float(Pval0, Type0), Size = erl_eval:partial_eval(Size1), {Psize,St2} = exprs([Size], St1), @@ -2033,8 +1999,8 @@ pat_alias(#c_alias{var=#c_var{name=V1}=Var1,pat=P1}, pat_alias(#c_alias{var=#c_var{}=Var,pat=P1}, P2) -> #c_alias{var=Var,pat=pat_alias(P1, P2)}; -pat_alias(#c_map{es=Es1}=M, #c_map{es=Es2}) -> - M#c_map{es=pat_alias_map_pairs(Es1 ++ Es2)}; +pat_alias(#imap{es=Es1}=M, #imap{es=Es2}) -> + M#imap{es=pat_alias_map_pairs(Es1 ++ Es2)}; pat_alias(P1, #c_var{}=Var) -> #c_alias{var=Var,pat=P1}; @@ -2068,11 +2034,11 @@ pat_alias_list(_, _) -> throw(nomatch). %% pattern_list([P], State) -> {[P],Exprs,St} pattern_list([P0|Ps0], St0) -> - {P1,Eps,St1} = pattern(P0, St0), - {Ps1,Epsl,St2} = pattern_list(Ps0, St1), - {[P1|Ps1], Eps ++ Epsl, St2}; + {P1,St1} = pattern(P0, St0), + {Ps1,St2} = pattern_list(Ps0, St1), + {[P1|Ps1],St2}; pattern_list([], St) -> - {[],[],St}. + {[],St}. string_to_conses(Line, Cs, Tail) -> foldr(fun (C, T) -> {cons,Line,{char,Line,C},T} end, Tail, Cs). @@ -2394,17 +2360,14 @@ upattern(#c_cons{hd=H0,tl=T0}=Cons, Ks, St0) -> upattern(#c_tuple{es=Es0}=Tuple, Ks, St0) -> {Es1,Esg,Esv,Eus,St1} = upattern_list(Es0, Ks, St0), {Tuple#c_tuple{es=Es1},Esg,Esv,Eus,St1}; -upattern(#c_map{es=Es0}=Map, Ks, St0) -> +upattern(#imap{es=Es0}=Map, Ks, St0) -> {Es1,Esg,Esv,Eus,St1} = upattern_list(Es0, Ks, St0), - {Map#c_map{es=Es1},Esg,Esv,Eus,St1}; -upattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,Ks,St0) -> + {Map#imap{es=Es1},Esg,Esv,Eus,St1}; +upattern(#imappair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,Ks,St0) -> {V,Vg,Vn,Vu,St1} = upattern(V0, Ks, St0), - % A variable key must be considered used here - Ku = case K0 of - #c_var{name=Name} -> [Name]; - _ -> [] - end, - {Pair#c_map_pair{val=V},Vg,Vn,union(Ku,Vu),St1}; + {K,St2} = uexprs(K0, Ks, St1), + Ku = used_in_expr(K), + {Pair#imappair{key=K,val=V},Vg,Vn,union(Ku, Vu),St2}; upattern(#ibinary{segments=Es0}=Bin, Ks, St0) -> {Es1,Esg,Esv,Eus,St1} = upat_bin(Es0, Ks, St0), {Bin#ibinary{segments=Es1},Esg,Esv,Eus,St1}; @@ -2571,9 +2534,9 @@ ren_pat(#c_alias{var=Var0,pat=Pat0}=Alias, Ks, {_,_}=Subs0, St0) -> {Var,Subs1,St1} = ren_pat(Var0, Ks, Subs0, St0), {Pat,Subs,St} = ren_pat(Pat0, Ks, Subs1, St1), {Alias#c_alias{var=Var,pat=Pat},Subs,St}; -ren_pat(#c_map{es=Es0}=Map, Ks, {_,_}=Subs0, St0) -> +ren_pat(#imap{es=Es0}=Map, Ks, {_,_}=Subs0, St0) -> {Es,Subs,St} = ren_pat_map(Es0, Ks, Subs0, St0), - {Map#c_map{es=Es},Subs,St}; + {Map#imap{es=Es},Subs,St}; ren_pat(#ibinary{segments=Es0}=P, Ks, {Isub,Osub0}, St0) -> {Es,_Isub,Osub,St} = ren_pat_bin(Es0, Ks, Isub, Osub0, St0), {P#ibinary{segments=Es},{Isub,Osub},St}; @@ -2596,10 +2559,10 @@ ren_pat_bin([#ibitstr{val=Val0,size=Sz0}=E|Es0], Ks, Isub0, Osub0, St0) -> ren_pat_bin([], _Ks, Isub, Osub, St) -> {[],Isub,Osub,St}. -ren_pat_map([#c_map_pair{val=Val0}=MapPair|Es0], Ks, Subs0, St0) -> +ren_pat_map([#imappair{val=Val0}=MapPair|Es0], Ks, Subs0, St0) -> {Val,Subs1,St1} = ren_pat(Val0, Ks, Subs0, St0), {Es,Subs,St} = ren_pat_map(Es0, Ks, Subs1, St1), - {[MapPair#c_map_pair{val=Val}|Es],Subs,St}; + {[MapPair#imappair{val=Val}|Es],Subs,St}; ren_pat_map([], _Ks, Subs, St) -> {[],Subs,St}. @@ -2655,17 +2618,17 @@ cpattern(#c_cons{hd=Hd,tl=Tl}=Cons) -> Cons#c_cons{hd=cpattern(Hd),tl=cpattern(Tl)}; cpattern(#c_tuple{es=Es}=Tup) -> Tup#c_tuple{es=cpattern_list(Es)}; -cpattern(#c_map{es=Es}=Map) -> - Map#c_map{es=cpat_map_pairs(Es)}; +cpattern(#imap{anno=#a{anno=Anno},es=Es}) -> + #c_map{anno=Anno,es=cpat_map_pairs(Es),is_pat=true}; cpattern(#ibinary{anno=#a{anno=Anno},segments=Segs0}) -> Segs = [cpat_bin_seg(S) || S <- Segs0], #c_binary{anno=Anno,segments=Segs}; cpattern(Other) -> Other. -cpat_map_pairs([#c_map_pair{key=Key0,val=Val0}=Pair0|T]) -> - Key = cpattern(Key0), +cpat_map_pairs([#imappair{anno=#a{anno=Anno},op=Op,key=Key0,val=Val0}|T]) -> + {Key,_,_} = cexprs(Key0, [], #core{}), Val = cpattern(Val0), - Pair = Pair0#c_map_pair{key=Key,val=Val}, + Pair = #c_map_pair{anno=Anno,op=Op,key=Key,val=Val}, [Pair|cpat_map_pairs(T)]; cpat_map_pairs([]) -> []. @@ -3185,18 +3148,57 @@ split_pat(Data, St0) -> Es = cerl:data_es(Data), split_data(Es, Type, St0, []). -split_map_pat([#c_map_pair{val=Val}=E0|Es], Map0, St0, Acc) -> - case split_pat(Val, St0) of +split_map_pat([#c_map_pair{key=Key,val=Val}=E0|Es], Map0, St0, Acc) -> + case eval_map_key(Key, E0, Es, Map0, St0) of none -> - split_map_pat(Es, Map0, St0, [E0|Acc]); - {Ps,Split,St1} -> - {Var,St} = new_var(St1), - E = E0#c_map_pair{val=Var}, - Map = Map0#c_map{es=reverse(Acc, [E|Es])}, - {Map,{split,[Var],Ps,Split},St} + case split_pat(Val, St0) of + none -> + split_map_pat(Es, Map0, St0, [E0|Acc]); + {Ps,Split,St1} -> + {Var,St} = new_var(St1), + E = E0#c_map_pair{val=Var}, + Map = Map0#c_map{es=reverse(Acc, [E|Es])}, + {Map,{split,[Var],Ps,Split},St} + end; + {MapVar,Split,St1} -> + BefMap0 = Map0#c_map{es=reverse(Acc)}, + BefMap = #c_alias{var=MapVar,pat=BefMap0}, + {BefMap,Split,St1} end; split_map_pat([], _, _, _) -> none. +eval_map_key(#c_var{}, _E, _Es, _Map, _St) -> + none; +eval_map_key(#c_literal{}, _E, _Es, _Map, _St) -> + none; +eval_map_key(Key, E0, Es, Map, St0) -> + {[KeyVar,MapVar],St1} = new_vars(2, St0), + E = E0#c_map_pair{key=KeyVar}, + AftMap0 = Map#c_map{es=[E|Es]}, + {Wrap,CaseArg,AftMap,St2} = wrap_map_key_fun(Key, KeyVar, MapVar, AftMap0, St1), + {MapVar,{split,[CaseArg],Wrap,AftMap,nil},St2}. + +wrap_map_key_fun(Key, KeyVar, MapVar, AftMap, St0) -> + case is_safe(Key) of + true -> + {fun(Body) -> + #c_let{vars=[KeyVar],arg=Key,body=Body} + end,MapVar,AftMap,St0}; + false -> + {[SuccVar|Evars],St} = new_vars(4, St0), + {fun(Body) -> + Try = #c_try{arg=Key,vars=[KeyVar], + body=#c_values{es=[#c_literal{val=true},KeyVar]}, + evars=Evars, + handler=#c_values{es=[#c_literal{val=false}, + #c_literal{val=false}]}}, + #c_let{vars=[SuccVar,KeyVar],arg=Try,body=Body} + end, + #c_tuple{es=[SuccVar,MapVar]}, + #c_tuple{es=[#c_literal{val=true},AftMap]}, + St} + end. + split_data([E|Es0], Type, St0, Acc) -> case split_pat(E, St0) of none -> diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl index 8b9dbe4aa0..9436ad5d53 100644 --- a/lib/compiler/test/error_SUITE.erl +++ b/lib/compiler/test/error_SUITE.erl @@ -270,8 +270,7 @@ maps_warnings(Config) when is_list(Config) -> id(I) -> I. ">>, [return], - {error,[{3,erl_lint,{unbound_var,'K'}}, - {6,erl_lint,illegal_map_key}],[]}} + {error,[{3,erl_lint,{unbound_var,'K'}}],[]}} ], [] = run2(Config, Ts1), ok. diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index 440b632381..b56a73f7bc 100644 --- a/lib/compiler/test/map_SUITE.erl +++ b/lib/compiler/test/map_SUITE.erl @@ -73,7 +73,10 @@ t_reused_key_variable/1, %% new in OTP 22 - t_mixed_clause/1,cover_beam_trim/1 + t_mixed_clause/1,cover_beam_trim/1, + + %% new in OTP 23 + t_key_expressions/1 ]). suite() -> []. @@ -130,7 +133,10 @@ all() -> t_reused_key_variable, %% new in OTP 22 - t_mixed_clause,cover_beam_trim + t_mixed_clause,cover_beam_trim, + + %% + t_key_expressions ]. groups() -> []. @@ -2191,6 +2197,59 @@ do_cover_beam_trim(Id, OldMax, Max, Id, M) -> #{Id:=Val} = id(M), Val. +t_key_expressions(_Config) -> + Int = id(42), + #{{tag,Int} := 42} = id(#{{tag,Int} => 42}), + #{{tag,Int+1} := 42} = id(#{{tag,Int+1} => 42}), + #{{a,b} := x, {tag,Int} := 42, Int := 0} = + id(#{{a,b} => x, {tag,Int} => 42, Int => 0}), + + F1 = fun(#{Int + 1 := Val}) -> Val end, + val = F1(#{43 => val}), + {'EXIT',_} = (catch F1(a)), + + F2 = fun(M, X, Y) -> + case M of + #{element(X, Y) := <<Sz:16,Bin:Sz/binary>>} -> + Bin; + #{} -> + not_found; + {A,B} -> + A + B + end + end, + <<"xyz">> = F2(#{b => <<3:16,"xyz">>}, 2, {a,b,c}), + not_found = F2(#{b => <<3:16,"xyz">>}, 999, {a,b,c}), + 13 = F2({6,7}, 1, 2), + + #{<<"Спутник"/utf8>> := 1} = id(#{<<"Спутник"/utf8>> => 1}), + + F3 = fun(Arg) -> + erase(once), + RunOnce = fun(I) -> + undefined = put(once, twice), + id(I) + end, + case RunOnce(Arg) of + #{{tag,<<Int:42>>} := Value} -> Value; + {X,Y} -> X + Y + end + end, + 10 = F3({7,3}), + whatever = F3(#{{tag,<<Int:42>>} => whatever}), + + F4 = fun(K1, K2, M) -> + case M of + #{K1 div K2 := V} -> V; + #{} -> no_match + end + end, + value = F4(42, 21, #{2 => value}), + no_match = F4(42, 21, #{}), + no_match = F4(42, 0, #{2 => value}), + no_match = F4(42, a, #{2 => value}), + + ok. %% aux diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 696216c483..64bbd568b0 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -81,7 +81,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> -type module_or_mfa() :: module() | mfa(). --type gexpr_context() :: 'guard' | 'bin_seg_size'. +-type gexpr_context() :: 'guard' | 'bin_seg_size' | 'map_key'. -record(typeinfo, {attr, line}). @@ -1794,19 +1794,16 @@ is_pattern_expr_1({op,_Line,Op,A1,A2}) -> is_pattern_expr_1(_Other) -> false. pattern_map(Ps, Vt, Old, Bvt, St) -> - foldl(fun - ({map_field_assoc,L,_,_}, {Psvt,Bvt0,St0}) -> - {Psvt,Bvt0,add_error(L, illegal_pattern, St0)}; - ({map_field_exact,L,K,V}, {Psvt,Bvt0,St0}) -> - case is_valid_map_key(K) of - true -> - {Kvt,St1} = expr(K, Vt, St0), - {Vvt,Bvt2,St2} = pattern(V, Vt, Old, Bvt, St1), - {vtmerge_pat(vtmerge_pat(Kvt, Vvt), Psvt), vtmerge_pat(Bvt0, Bvt2), St2}; - false -> - {Psvt,Bvt0,add_error(L, illegal_map_key, St0)} - end - end, {[],[],St}, Ps). + foldl(fun({map_field_assoc,L,_,_}, {Psvt,Bvt0,St0}) -> + {Psvt,Bvt0,add_error(L, illegal_pattern, St0)}; + ({map_field_exact,_L,K,V}, {Psvt,Bvt0,St0}) -> + St1 = St0#lint{gexpr_context=map_key}, + {Kvt,St2} = gexpr(K, Vt, St1), + {Vvt,Bvt2,St3} = pattern(V, Vt, Old, Bvt, St2), + {vtmerge_pat(vtmerge_pat(Kvt, Vvt), Psvt), + vtmerge_pat(Bvt0, Bvt2), + St3} + end, {[],[],St}, Ps). %% pattern_bin([Element], VarTable, Old, BinVarTable, State) -> %% {UpdVarTable,UpdBinVarTable,State}. @@ -2618,63 +2615,6 @@ is_valid_call(Call) -> _ -> true end. -%% is_valid_map_key(K) -> true | false -%% variables are allowed for patterns only at the top of the tree - -is_valid_map_key({var,_,_}) -> true; -is_valid_map_key(K) -> is_valid_map_key_value(K). -is_valid_map_key_value(K) -> - case K of - {var,_,_} -> false; - {char,_,_} -> true; - {integer,_,_} -> true; - {float,_,_} -> true; - {string,_,_} -> true; - {nil,_} -> true; - {atom,_,_} -> true; - {cons,_,H,T} -> - is_valid_map_key_value(H) andalso - is_valid_map_key_value(T); - {tuple,_,Es} -> - foldl(fun(E,B) -> - B andalso is_valid_map_key_value(E) - end,true,Es); - {map,_,Arg,Ps} -> - % only check for value expressions to be valid - % invalid map expressions are later checked in - % core and kernel - is_valid_map_key_value(Arg) andalso foldl(fun - ({Tag,_,Ke,Ve},B) when Tag =:= map_field_assoc; - Tag =:= map_field_exact -> - B andalso is_valid_map_key_value(Ke) - andalso is_valid_map_key_value(Ve); - (_,_) -> false - end,true,Ps); - {map,_,Ps} -> - foldl(fun - ({Tag,_,Ke,Ve},B) when Tag =:= map_field_assoc; - Tag =:= map_field_exact -> - B andalso is_valid_map_key_value(Ke) - andalso is_valid_map_key_value(Ve); - (_,_) -> false - end, true, Ps); - {record,_,_,Fs} -> - foldl(fun - ({record_field,_,Ke,Ve},B) -> - B andalso is_valid_map_key_value(Ke) - andalso is_valid_map_key_value(Ve) - end,true,Fs); - {bin,_,Es} -> - % only check for value expressions to be valid - % invalid binary expressions are later checked in - % core and kernel - foldl(fun - ({bin_element,_,E,_,_},B) -> - B andalso is_valid_map_key_value(E) - end,true,Es); - Val -> is_pattern_expr(Val) - end. - %% record_def(Line, RecordName, [RecField], State) -> State. %% Add a record definition if it does not already exist. Normalise %% so that all fields have explicit initial value. diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index 81990ec747..4448a919bc 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -3799,7 +3799,7 @@ maps(Config) -> ">>, [], {errors,[{4,erl_lint,illegal_map_construction}, - {6,erl_lint,illegal_map_key}],[]}}, + {6,erl_lint,{unbound_var,'V'}}],[]}}, {unused_vars_with_empty_maps, <<"t(Foo, Bar, Baz) -> {#{},#{}}.">>, [warn_unused_variables], |