File 5511-Correct-scope-of-variables.patch of Package erlang
From 9006ea80b844cbaa38450a31807cdf5b7a14b651 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Thu, 18 Nov 2021 12:26:33 +0100
Subject: [PATCH] Correct scope of variables
Consider this example:
main(_) ->
_ = (X = 42) + fun() -> X = 0 end(),
io:format("~p\n", [X]).
When run as an `escript`, we will get different results depending
on whether we run it using `erl_eval` or using the Erlang compiler.
Here is the result using `erl_eval` (the default):
$ escript t.erl
42
Here is the result using the compiler:
$ escript -c t.erl
escript: exception error: no match of right hand side value 0
At least one of the results must be wrong. Which?
The compiler is wrong.
A binary operator (such as `+`) is allowed to evaluate its operands
in any order. Thus, code that depends on the left hand side of
a binary operator being evaluated before the right hand side is
illegal and will be rejected by the compiler. For example:
bar(A) ->
(X = A) + (2 * X).
Compilation will fail with the following message:
t.erl:5:20: variable 'X' is unbound
% 5| (X = A) + (2 * X).
% | ^
Therefore, when we have the expression:
(X = 42) + fun() -> X = 0 end()
the variable `X` is not bound when evaluating the right hand side
operand for `+` and the `X` bound in the fun body is another variable
named `X`, unrelated to the `X` bound on the left hand side of `+`.
This commit corrects the compiler so that it generates code that
evalutes the example in the same way as `erl_eval` does.
Closes #5379
---
lib/compiler/src/v3_core.erl | 315 +++++++++++++++++++++++++++-----
lib/compiler/test/fun_SUITE.erl | 230 ++++++++++++++++++++++-
2 files changed, 494 insertions(+), 51 deletions(-)
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 1eb7f9778b..05666005ed 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -109,6 +109,7 @@
-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(iexprs, {anno=#a{},bodies=[]}).
-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}).
@@ -565,6 +566,8 @@ unforce(E, Eps, Vs) ->
Tree = unforce_tree(Eps++[E], gb_trees:empty()),
unforce(Tree, Vs).
+unforce_tree([#iexprs{bodies=Exprs}|Es], D0) ->
+ unforce_tree(lists:append(Exprs) ++ Es, D0);
unforce_tree([#iset{var=#c_var{name=V},arg=Arg0}|Es], D0) ->
Arg = unforce_tree_subst(Arg0, D0),
D = gb_trees:insert(V, Arg, D0),
@@ -627,10 +630,9 @@ expr({atom,L,A}, St) -> {#c_literal{anno=full_anno(L, St),val=A},[],St};
expr({nil,L}, St) -> {#c_literal{anno=full_anno(L, St),val=[]},[],St};
expr({string,L,S}, St) -> {#c_literal{anno=full_anno(L, St),val=S},[],St};
expr({cons,L,H0,T0}, St0) ->
- {H1,Hps,St1} = safe(H0, St0),
- {T1,Tps,St2} = safe(T0, St1),
- A = full_anno(L, St2),
- {annotate_cons(A, H1, T1, St2),Hps ++ Tps,St2};
+ {[H1,T1],Eps,St1} = safe_list([H0,T0], St0),
+ A = full_anno(L, St1),
+ {annotate_cons(A, H1, T1, St1),Eps,St1};
expr({lc,L,E,Qs0}, St0) ->
{Qs1,St1} = preprocess_quals(L, Qs0, St0),
lc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1);
@@ -1795,6 +1797,33 @@ force_novars(#c_map{}=Bin, St) -> {Bin,[],St};
force_novars(Ce, St) ->
force_safe(Ce, St).
+
+%% safe_list(Expr, State) -> {Safe,[PreExpr],State}.
+%% Generate an internal safe expression for a list of
+%% expressions.
+
+safe_list(Es, St0) ->
+ {Vs,Eps0,St} =
+ foldr(fun (E, {Ces,Eps,Sti0}) ->
+ {Ce,Ep,Sti1} = safe(E, Sti0),
+ case Eps of
+ [[#iexprs{bodies=Bs}]|T] ->
+ %% A cons within a cons.
+ {[Ce|Ces],[Ep|Bs]++T,Sti1};
+ _ ->
+ {[Ce|Ces],[Ep|Eps],Sti1}
+ end
+ end, {[],[],St0}, Es),
+ case [Ep || [_|_]=Ep <- Eps0] of
+ [] ->
+ {Vs,[],St};
+ [Ep] ->
+ {Vs,Ep,St};
+ [_|_]=Eps ->
+ %% Two or more bodies. They see the same variables.
+ {Vs,[#iexprs{bodies=Eps}],St}
+ 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
@@ -1805,12 +1834,6 @@ safe(E0, St0) ->
{Se,Sps,St2} = force_safe(E1, St1),
{Se,Eps ++ Sps,St2}.
-safe_list(Es, St) ->
- foldr(fun (E, {Ces,Esp,St0}) ->
- {Ce,Ep,St1} = safe(E, St0),
- {[Ce|Ces],Ep ++ Esp,St1}
- end, {[],[],St}, Es).
-
force_safe(#imatch{pat=P,arg=E}=Imatch, St0) ->
{Le,Lps0,St1} = force_safe(E, St0),
Lps = Lps0 ++ [Imatch#imatch{arg=Le}],
@@ -2147,7 +2170,155 @@ annotate_cons(A, H, T, #core{dialyzer=Dialyzer}) ->
ann_c_cons(A, H, T)
end.
-ubody(B, St) -> uexpr(B, [], St).
+%%%
+%%% Here follows an abstract data structure to help us handle Erlang's
+%%% implicit matching that occurs when a variable is bound more than
+%%% once:
+%%%
+%%% X = Expr1(),
+%%% X = Expr2()
+%%%
+%%% What is implicit in Erlang, must be explicit in Core Erlang; that
+%%% is, repeated variables must be eliminated and explicit matching
+%%% must be added. For simplicity, examples that follow will be given
+%%% in Erlang and not in Core Erlang. Here is how the example can be
+%%% rewritten in Erlang to eliminate the repeated variable:
+%%%
+%%% X = Expr1(),
+%%% X1 = Expr2(),
+%%% if
+%%% X1 =:= X -> X;
+%%% true -> error({badmatch,X1})
+%%% end
+%%%
+%%% To implement the renaming, keeping a set of the variables that
+%%% have been bound so far is **almost** sufficient. When a variable
+%%% in the set is bound a again, it will be renamed and a `case` with
+%%% guard test will be added.
+%%%
+%%% Here is another example:
+%%%
+%%% (X=A) + (X=B)
+%%%
+%%% Note that the operands for a binary operands are allowed to be
+%%% evaluated in any order. Therefore, variables bound on the left
+%%% hand side must not referenced on the right hand side, and vice
+%%% versa. If a variable is bound on both sides, it must be bound
+%%% to the same value.
+%%%
+%%% Using the simple scheme of keeping track of known variables,
+%%% the example can be rewritten like this:
+%%%
+%%% X = A,
+%%% X1 = B,
+%%% if
+%%% X1 =:= X -> ok;
+%%% true -> error({badmatch,X1})
+%%% end,
+%%% X + X1
+%%%
+%%% However, this simple scheme of keeping all previously bound variables in
+%%% a set breaks down for this example:
+%%%
+%%% (X=A) + fun() -> X = B end()
+%%%
+%%% The rewritten code would be:
+%%%
+%%% X = A,
+%%% Tmp = fun() ->
+%%% X1 = B,
+%%% if
+%%% X1 =:= X -> ok;
+%%% true -> error({badmatch,X1})
+%%% end
+%%% end(),
+%%% X + Tmp
+%%%
+%%% That is wrong, because the binding of `X` created on the left hand
+%%% side of `+` must not be seen inside the fun. The correct rewrite
+%%% would be like this:
+%%%
+%%% X = A,
+%%% Tmp = fun() ->
+%%% X1 = B
+%%% end(),
+%%% X + Tmp
+%%%
+%%% To correctly rewrite fun bodies, we will need to keep addtional
+%%% information in a record so that we can remove `X` from the known
+%%% variables when rewriting the body of the fun.
+%%%
+
+-record(known, {base=[],ks=[],prev_ks=[]}).
+
+known_init() ->
+ #known{}.
+
+%% known_get(#known{}) -> [KnownVar].
+%% Get the currently known variables.
+
+known_get(#known{ks=Ks}) ->
+ Ks.
+
+%% known_start_group(#known{}) -> #known{}.
+
+known_start_group(#known{base=OldBase,ks=Ks,prev_ks=PrevKs}=K) ->
+ K#known{base=[Ks|OldBase],prev_ks=[[]|PrevKs]}.
+
+%% known_end_body(#known{}) -> #known{}.
+
+known_end_body(#known{ks=Ks,prev_ks=[_|OldPrevKs]}=K) ->
+ K#known{prev_ks=[Ks|OldPrevKs]}.
+
+%% known_end_group(#known{}) -> #known{}.
+%% Consolidate the known variables after having processed the
+%% last body in a group of bodies that see the same bindings.
+
+known_end_group(#known{base=[_|OldBase],prev_ks=[_|OldPrevKs]}=K) ->
+ K#known{base=OldBase,prev_ks=OldPrevKs}.
+
+%% known_union(#known{}, KnownVarsSet) -> #known{}.
+%% Update the known variables to be the union of the previous
+%% known variables and the set KnownVarsSet.
+
+known_union(#known{ks=Ks}=K, Set) ->
+ K#known{ks=union(Ks, Set)}.
+
+%% known_bind(#known{}, BoundVarsSet) -> #known{}.
+%% Add variables that are known to be bound in the current
+%% body.
+
+known_bind(#known{prev_ks=[PrevKs0|OldPrevKs]}=K, BoundVs) ->
+ PrevKs = subtract(PrevKs0, BoundVs),
+ K#known{prev_ks=[PrevKs|OldPrevKs]};
+known_bind(#known{}=K, _) -> K.
+
+%% known_in_fun(#known{}) -> #known{}.
+%% Update the known variables to only the set of variables that
+%% should be known when entering the fun.
+
+known_in_fun(#known{base=[BaseKs|_],ks=Ks0,prev_ks=[PrevKs|_]}=K) ->
+ %% Within a group of bodies that see the same bindings, calculate
+ %% the known variables for a fun. Example:
+ %%
+ %% A = 1,
+ %% {X = 2, fun() -> X = 99, A = 1 end()}.
+ %%
+ %% In this example:
+ %%
+ %% BaseKs = ['A'], Ks0 = ['A','X'], PrevKs = ['A','X']
+ %%
+ %% Thus, only `A` is known when entering the fun.
+
+ Ks = union(BaseKs, subtract(Ks0, PrevKs)),
+ K#known{base=[],ks=Ks,prev_ks=[]};
+known_in_fun(#known{}=K) -> K.
+
+%%%
+%%% End of abstract data type for known variables.
+%%%
+
+ubody(B, St) -> uexpr(B, known_init(), St).
%% ufun_clauses([Lclause], [KnownVar], State) -> {[Lclause],State}.
@@ -2207,15 +2378,41 @@ do_uclause(#iclause{anno=A0,pats=Ps0,guard=G0,body=B0}, Ks0, St0) ->
false ->
{Pg0,A0}
end,
- Pu = union(Pus, intersection(Pvs, Ks0)),
+ Pu = union(Pus, intersection(Pvs, known_get(Ks0))),
Pn = subtract(Pvs, Pu),
- Ks1 = union(Pn, Ks0),
+ Ks1 = known_union(Ks0, Pn),
{G1,St2} = uguard(Pg, G0, Ks1, St1),
Gu = used_in_any(G1),
Gn = new_in_any(G1),
- Ks2 = union(Gn, Ks1),
- {B1,St3} = uexprs(B0, Ks2, St2),
- Used = intersection(union([Pu,Gu,used_in_any(B1)]), Ks0),
+ Ks2 = known_union(Ks1, Gn),
+
+ %% Consider this example:
+ %%
+ %% {X = A,
+ %% begin X = B, fun() -> X = C end() end}.
+ %%
+ %% At this point it has been rewritten to something similar
+ %% like this (the fun body has not been rewritten yet):
+ %%
+ %% {X = A,
+ %% begin
+ %% X1 = B,
+ %% if
+ %% X1 =:= X -> ok;
+ %% true -> error({badmatch,X1})
+ %% end,
+ %% fun() -> ... end() end
+ %% end}.
+ %%
+ %% In this example, the variable `X` is a known variable that must
+ %% be passed into the fun body (because of `X = B` above). To ensure
+ %% that it is, we must call known_bind/2 with the variables used
+ %% in the guard (`X1` and `X`; any variables used must surely be
+ %% bound).
+
+ Ks3 = known_bind(Ks2, Gu),
+ {B1,_,St3} = uexprs(B0, Ks3, St2),
+ Used = intersection(union([Pu,Gu,used_in_any(B1)]), known_get(Ks0)),
New = union([Pn,Gn,new_in_any(B1)]),
{#iclause{anno=A,pats=Ps1,guard=G1,body=B1},Pvs,Used,New,St3}.
@@ -2240,10 +2437,31 @@ uguard(Pg, Gs0, Ks, St0) ->
St3}
end, {Gs0,St0}, Pg),
%%ok = io:fwrite("core ~w: ~p~n", [?LINE,Gs3]),
- uexprs(Gs3, Ks, St5).
+ {Gs4,_,St6} = uexprs(Gs3, Ks, St5),
+ {Gs4,St6}.
+
+%% ulinearize_exprs([[Kexpr]], [Kexpr]) -> [Kexpr].
+%% Linearize a group of bodies to a linear list of Kernel expressions
+%% while inserting markers for the end of each body and the group
+%% itself.
+
+ulinearize_exprs([Bs|Bss], Les) ->
+ [known_end_body|Bs] ++ ulinearize_exprs(Bss, Les);
+ulinearize_exprs([], Les) ->
+ [known_end_group|Les].
%% uexprs([Kexpr], [KnownVar], State) -> {[Kexpr],State}.
+uexprs([known_end_body|Les], Ks0, St0) ->
+ Ks1 = known_end_body(Ks0),
+ uexprs(Les, Ks1, St0);
+uexprs([known_end_group|Les], Ks0, St0) ->
+ Ks1 = known_end_group(Ks0),
+ uexprs(Les, Ks1, St0);
+uexprs([#iexprs{bodies=Es0}|Les], Ks0, St0) ->
+ Es = ulinearize_exprs(Es0, Les),
+ Ks1 = known_start_group(Ks0),
+ uexprs(Es, Ks1, St0);
uexprs([#imatch{anno=A,pat=P0,arg=Arg,fc=Fc}|Les], Ks, St0) ->
case upat_is_new_var(P0, Ks) of
true ->
@@ -2262,18 +2480,18 @@ uexprs([#imatch{anno=A,pat=P0,arg=Arg,fc=Fc}|Les], Ks, St0) ->
uexprs([#icase{anno=A,args=[Arg],
clauses=[Mc],fc=Fc}], Ks, St0)
end;
-uexprs([Le0|Les0], Ks, St0) ->
- {Le1,St1} = uexpr(Le0, Ks, St0),
- {Les1,St2} = uexprs(Les0, union((get_anno(Le1))#a.ns, Ks), St1),
- {[Le1|Les1],St2};
-uexprs([], _, St) -> {[],St}.
+uexprs([Le0|Les0], Ks0, St0) ->
+ {Le1,St1} = uexpr(Le0, Ks0, St0),
+ {Les1,Ks,St2} = uexprs(Les0, known_union(Ks0, (get_anno(Le1))#a.ns), St1),
+ {[Le1|Les1],Ks,St2};
+uexprs([], Ks, St) -> {[],Ks,St}.
%% upat_is_new_var(Pattern, [KnownVar]) -> true|false.
%% Test whether the pattern is a single, previously unknown
%% variable.
upat_is_new_var(#c_var{name=V}, Ks) ->
- not is_element(V, Ks);
+ not is_element(V, known_get(Ks));
upat_is_new_var(_, _) ->
false.
@@ -2301,7 +2519,7 @@ uexpr(#iletrec{anno=A,defs=Fs0,body=B0}, Ks, St0) ->
{F1,S1} = uexpr(F0, Ks, S0),
{{Name,F1},S1}
end, St0, Fs0),
- {B1,St2} = uexprs(B0, Ks, St1),
+ {B1,_,St2} = uexprs(B0, Ks, St1),
Used = used_in_any(map(fun ({_,F}) -> F end, Fs1) ++ B1),
{#iletrec{anno=A#a{us=Used,ns=[]},defs=Fs1,body=B1},St2};
uexpr(#icase{anno=#a{anno=Anno}=A,args=As0,clauses=Cs0,fc=Fc0}, Ks, St0) ->
@@ -2316,7 +2534,7 @@ uexpr(#icase{anno=#a{anno=Anno}=A,args=As0,clauses=Cs0,fc=Fc0}, Ks, St0) ->
end,
{#icase{anno=A#a{us=Used,ns=New},args=As1,clauses=Cs1,fc=Fc1},St3};
uexpr(#ifun{anno=A0,id=Id,vars=As,clauses=Cs0,fc=Fc0,name=Name}=Fun0, Ks0, St0) ->
- {Fun1,St2} = case Ks0 of
+ {Fun1,St2} = case known_get(Ks0) of
[] ->
{Fun0,St0};
[_|_] ->
@@ -2327,12 +2545,13 @@ uexpr(#ifun{anno=A0,id=Id,vars=As,clauses=Cs0,fc=Fc0,name=Name}=Fun0, Ks0, St0)
Avs = lit_list_vars(As),
Ks1 = case Name of
unnamed -> Ks0;
- {named,FName} -> union(subtract([FName], Avs), Ks0)
+ {named,FName} -> known_union(Ks0, subtract([FName], Avs))
end,
- Ks2 = union(Avs, Ks1),
- {Cs3,St3} = ufun_clauses(Cs2, Ks2, St2),
- {Fc1,St4} = ufun_clause(Fc0, Ks2, St3),
- Used = subtract(intersection(used_in_any(Cs3), Ks1), Avs),
+ Ks2 = known_union(Ks1, Avs),
+ KnownInFun = known_in_fun(Ks2),
+ {Cs3,St3} = ufun_clauses(Cs2, KnownInFun, St2),
+ {Fc1,St4} = ufun_clause(Fc0, KnownInFun, St3),
+ Used = subtract(intersection(used_in_any(Cs3), known_get(Ks1)), Avs),
A1 = A0#a{us=Used,ns=[]},
{#ifun{anno=A1,id=Id,vars=As,clauses=Cs3,fc=Fc1,name=Name},St4};
uexpr(#iapply{anno=A,op=Op,args=As}, _, St) ->
@@ -2349,15 +2568,15 @@ uexpr(#itry{anno=A,args=As0,vars=Vs,body=Bs0,evars=Evs,handler=Hs0}, Ks, St0) ->
%% variables bound in the argument (the code between the 'try' and
%% the 'of' keywords) are exported to the body (the code following
%% the 'of' keyword).
- {As1,St1} = uexprs(As0, Ks, St0),
- ArgKs = union(Ks, new_in_any(As1)),
- {Bs1,St2} = uexprs(Bs0, ArgKs, St1),
- {Hs1,St3} = uexprs(Hs0, Ks, St2),
- Used = intersection(used_in_any(Bs1++Hs1++As1), Ks),
+ {As1,_,St1} = uexprs(As0, Ks, St0),
+ ArgKs = known_union(Ks, new_in_any(As1)),
+ {Bs1,_,St2} = uexprs(Bs0, ArgKs, St1),
+ {Hs1,_,St3} = uexprs(Hs0, Ks, St2),
+ Used = intersection(used_in_any(Bs1++Hs1++As1), known_get(Ks)),
{#itry{anno=A#a{us=Used,ns=[]},
args=As1,vars=Vs,body=Bs1,evars=Evs,handler=Hs1},St3};
uexpr(#icatch{anno=A,body=Es0}, Ks, St0) ->
- {Es1,St1} = uexprs(Es0, Ks, St0),
+ {Es1,_,St1} = uexprs(Es0, Ks, St0),
{#icatch{anno=A#a{us=used_in_any(Es1)},body=Es1},St1};
uexpr(#ireceive1{anno=A,clauses=Cs0}, Ks, St0) ->
{Cs1,St1} = uclauses(Cs0, Ks, St0),
@@ -2367,7 +2586,7 @@ uexpr(#ireceive2{anno=A,clauses=Cs0,timeout=Te0,action=Tes0}, Ks, St0) ->
%% Te0 will never generate new variables.
{Te1,St1} = uexpr(Te0, Ks, St0),
{Cs1,St2} = uclauses(Cs0, Ks, St1),
- {Tes1,St3} = uexprs(Tes0, Ks, St2),
+ {Tes1,_,St3} = uexprs(Tes0, Ks, St2),
Used = union([used_in_any(Cs1),used_in_any(Tes1),(get_anno(Te1))#a.us]),
New = case Cs1 of
[] -> new_in_any(Tes1);
@@ -2376,7 +2595,7 @@ uexpr(#ireceive2{anno=A,clauses=Cs0,timeout=Te0,action=Tes0}, Ks, St0) ->
{#ireceive2{anno=A#a{us=Used,ns=New},
clauses=Cs1,timeout=Te1,action=Tes1},St3};
uexpr(#iprotect{anno=A,body=Es0}, Ks, St0) ->
- {Es1,St1} = uexprs(Es0, Ks, St0),
+ {Es1,_,St1} = uexprs(Es0, Ks, St0),
Used = used_in_any(Es1),
{#iprotect{anno=A#a{us=Used},body=Es1},St1}; %No new variables escape!
uexpr(#ibinary{anno=A,segments=Ss}, _, St) ->
@@ -2401,7 +2620,7 @@ upattern(#c_var{name='_'}, _, St0) ->
{New,St1} = new_var_name(St0),
{#c_var{name=New},[],[New],[],St1};
upattern(#c_var{name=V}=Var, Ks, St0) ->
- case is_element(V, Ks) of
+ case is_element(V, known_get(Ks)) of
true ->
{N,St1} = new_var_name(St0),
New = #c_var{name=N},
@@ -2416,7 +2635,7 @@ upattern(#c_var{name=V}=Var, Ks, St0) ->
end;
upattern(#c_cons{hd=H0,tl=T0}=Cons, Ks, St0) ->
{H1,Hg,Hv,Hu,St1} = upattern(H0, Ks, St0),
- {T1,Tg,Tv,Tu,St2} = upattern(T0, union(Hv, Ks), St1),
+ {T1,Tg,Tv,Tu,St2} = upattern(T0, known_union(Ks, Hv), St1),
{Cons#c_cons{hd=H1,tl=T1},Hg ++ Tg,union(Hv, Tv),union(Hu, Tu),St2};
upattern(#c_tuple{es=Es0}=Tuple, Ks, St0) ->
{Es1,Esg,Esv,Eus,St1} = upattern_list(Es0, Ks, St0),
@@ -2426,7 +2645,7 @@ upattern(#imap{es=Es0}=Map, 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),
- {K,St2} = uexprs(K0, Ks, 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) ->
@@ -2434,7 +2653,7 @@ upattern(#ibinary{segments=Es0}=Bin, Ks, St0) ->
{Bin#ibinary{segments=Es1},Esg,Esv,Eus,St1};
upattern(#c_alias{var=V0,pat=P0}=Alias, Ks, St0) ->
{V1,Vg,Vv,Vu,St1} = upattern(V0, Ks, St0),
- {P1,Pg,Pv,Pu,St2} = upattern(P0, union(Vv, Ks), St1),
+ {P1,Pg,Pv,Pu,St2} = upattern(P0, known_union(Ks, Vv), St1),
{Alias#c_alias{var=V1,pat=P1},Vg ++ Pg,union(Vv, Pv),union(Vu, Pu),St2};
upattern(Other, _, St) -> {Other,[],[],[],St}. %Constants
@@ -2443,7 +2662,7 @@ upattern(Other, _, St) -> {Other,[],[],[],St}. %Constants
upattern_list([P0|Ps0], Ks, St0) ->
{P1,Pg,Pv,Pu,St1} = upattern(P0, Ks, St0),
- {Ps1,Psg,Psv,Psu,St2} = upattern_list(Ps0, union(Pv, Ks), St1),
+ {Ps1,Psg,Psv,Psu,St2} = upattern_list(Ps0, known_union(Ks, Pv), St1),
{[P1|Ps1],Pg ++ Psg,union(Pv, Psv),union(Pu, Psu),St2};
upattern_list([], _, St) -> {[],[],[],[],St}.
@@ -2470,7 +2689,7 @@ upat_bin(Es0, Ks, St0) ->
%% {[Pat],[GuardTest],[NewVar],[UsedVar],State}.
upat_bin([P0|Ps0], Ks, Bs, St0) ->
{P1,Pg,Pv,Pu,Bs1,St1} = upat_element(P0, Ks, Bs, St0),
- {Ps1,Psg,Psv,Psu,St2} = upat_bin(Ps0, union(Pv, Ks), Bs1, St1),
+ {Ps1,Psg,Psv,Psu,St2} = upat_bin(Ps0, known_union(Ks, Pv), Bs1, St1),
{[P1|Ps1],Pg ++ Psg,union(Pv, Psv),union(Pu, Psu),St2};
upat_bin([], _, _, St) -> {[],[],[],[],St}.
@@ -2493,16 +2712,16 @@ upat_element(#ibitstr{val=H0,size=Sz0}=Seg, Ks, Bs0, St0) ->
case Sz0 of
[#c_var{name=Vname}] ->
{Sz1,Us} = rename_bitstr_size(Vname, Bs0),
- {Sz2,St2} = uexprs([Sz1], Ks, St1),
+ {Sz2,_,St2} = uexprs([Sz1], Ks, St1),
{Seg#ibitstr{val=H1,size=Sz2},Hg,Hv,Us,Bs1,St2};
[#c_literal{}] ->
- {Sz1,St2} = uexprs(Sz0, Ks, St1),
+ {Sz1,_,St2} = uexprs(Sz0, Ks, St1),
Us = [],
{Seg#ibitstr{val=H1,size=Sz1},Hg,Hv,Us,Bs1,St2};
Expr when is_list(Expr) ->
Sz1 = [#iset{var=#c_var{name=Old},arg=#c_var{name=New}} ||
{Old,New} <- Bs0] ++ Expr,
- {Sz2,St2} = uexprs(Sz1, Ks, St1),
+ {Sz2,_,St2} = uexprs(Sz1, Ks, St1),
Us = used_in_expr(Sz2),
{Seg#ibitstr{val=H1,size=Sz2},Hg,Hv,Us,Bs1,St2}
end.
@@ -2576,7 +2795,7 @@ ren_pats([], _Ks, {_,_}=Subs, St) ->
ren_pat(#c_var{name='_'}=P, _Ks, Subs, St) ->
{P,Subs,St};
ren_pat(#c_var{name=V}=Old, Ks, {Isub0,Osub0}=Subs, St0) ->
- case member(V, Ks) of
+ case member(V, known_get(Ks)) of
true ->
case ren_is_subst(V, Osub0) of
{yes,New} ->
diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl
index 0bf014f8bc..0906639dc9 100644
--- a/lib/compiler/test/fun_SUITE.erl
+++ b/lib/compiler/test/fun_SUITE.erl
@@ -23,7 +23,7 @@
init_per_group/2,end_per_group/2,
test1/1,overwritten_fun/1,otp_7202/1,bif_fun/1,
external/1,eep37/1,eep37_dup/1,badarity/1,badfun/1,
- duplicated_fun/1,unused_fun/1]).
+ duplicated_fun/1,unused_fun/1,parallel_scopes/1]).
%% Internal exports.
-export([call_me/1,dup1/0,dup2/0]).
@@ -38,7 +38,8 @@ groups() ->
groups() ->
[{p,[parallel],
[test1,overwritten_fun,otp_7202,bif_fun,external,eep37,
- eep37_dup,badarity,badfun,duplicated_fun,unused_fun]}].
+ eep37_dup,badarity,badfun,duplicated_fun,unused_fun,
+ parallel_scopes]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -295,5 +296,229 @@ unused_fun(_Config) ->
catch _ -> ok end,
ok.
+parallel_scopes(_Config) ->
+ 1 = parallel_scopes_1a(),
+ 1 = parallel_scopes_1b(),
+ {'EXIT',{{badmatch,99},_}} = catch parallel_scopes_1c(),
+
+ 10 = parallel_scopes_2a(),
+ {'EXIT',{{badmatch,15},_}} = catch parallel_scopes_2b(),
+ 500 = parallel_scopes_2c(500, 500),
+ {'EXIT',{{badmatch,1000},_}} = catch parallel_scopes_2c(500, 1000),
+ 600 = parallel_scopes_2d(600, 600),
+ {'EXIT',{{badmatch,1000},_}} = catch parallel_scopes_2d(600, 1000),
+ {a,20} = parallel_scopes_2e(20, 20),
+ {'EXIT',{{badmatch,{a,25}},_}} = catch parallel_scopes_2e(20, 25),
+
+ {[42,2],42,a} = parallel_scopes_3(a),
+
+ 42 = parallel_scopes_4a(id(42), id(42)),
+ {'EXIT',{{badmatch,77},_}} = catch parallel_scopes_4a(42, 77),
+ 42 = parallel_scopes_4b(id(42), id(42)),
+ {'EXIT',{{badmatch,77},_}} = catch parallel_scopes_4b(42, 77),
+ [same,2,same,2] = parallel_scopes_4c(id(same), id(same)),
+ {'EXIT',{{badmatch,55},_}} = catch parallel_scopes_4c(42, 55),
+
+ 33 = parallel_scopes_5(id(33), id(33)),
+ {'EXIT',{{badmatch,44},_}} = catch parallel_scopes_5(33, 44),
+
+ 99 = parallel_scopes_6(id(99), id(99)),
+ {'EXIT',{{badmatch,88},_}} = catch parallel_scopes_6(77, 88),
+
+ 99 = parallel_scopes_7(id(99), id(99)),
+ {'EXIT',{{badmatch,88},_}} = catch parallel_scopes_7(77, 88),
+
+ 199 = parallel_scopes_8(id(199), id(199)),
+ {'EXIT',{{badmatch,200},_}} = catch parallel_scopes_8(id(199), id(200)),
+
+ {299,299+299} = parallel_scopes_9(id(299), id(299), id(299+299)),
+ {'EXIT',{{badmatch,300},_}} = catch parallel_scopes_9(id(299), id(300), id(0)),
+ {'EXIT',{{badmatch,0},_}} = catch parallel_scopes_9(id(299), id(299), id(0)),
+
+ 999 = parallel_scopes_10(false, 999, ignored, 999),
+ {'EXIT',{{badmatch,999},_}} = catch parallel_scopes_10(false, 700, ignored, 700),
+ {'EXIT',{{badmatch,1000},_}} = catch parallel_scopes_10(false, 999, ignored, 1000),
+ 999 = parallel_scopes_10(true, 999, 999, ignored),
+ 333 = parallel_scopes_10(true, 333, 333, ignored),
+ {'EXIT',{{badmatch,901},_}} = catch parallel_scopes_10(true, 900, 901, ignored),
+
+ 889 = parallel_scopes_11(889, 889, 889),
+ {'EXIT',{{badmatch,800},_}} = catch parallel_scopes_11(889, 800, 889),
+ {'EXIT',{{badmatch,810},_}} = catch parallel_scopes_11(889, 889, 810),
+ {'EXIT',{{badmatch,889},_}} = catch parallel_scopes_11(a, a, a),
+
+ 333 = parallel_scopes_12(333, 333, 333),
+ {'EXIT',{{badmatch,other},_}} = catch parallel_scopes_12(333, other, 333),
+ {'EXIT',{{badmatch,nope},_}} = catch parallel_scopes_12(333, 333, nope),
+
+ [1,100] = parallel_scopes_13(99, 100),
+ {'EXIT',{{badmatch,no},_}} = catch parallel_scopes_13(no, 100),
+ {'EXIT',{{badmatch,nope},_}} = catch parallel_scopes_13(99, nope),
+
+ ok.
+
+parallel_scopes_1a() ->
+ (begin X=1, true end
+ and
+ begin F=(fun () -> X=2 end), F(), true end) andalso X.
+
+parallel_scopes_1b() ->
+ (begin X=1, true end
+ and
+ begin F=(fun () -> X=2 end), F(), true end) andalso (X = 1).
+
+parallel_scopes_1c() ->
+ (begin X=1, true end
+ and
+ begin F=(fun () -> X=2 end), F(), true end) andalso (X = 99).
+
+parallel_scopes_2a() ->
+ begin X=10, true end
+ and
+ begin F=(fun () -> X=20 end), F(), true end
+ and
+ begin X=10, true end andalso X.
+
+parallel_scopes_2b() ->
+ begin X=10, true end
+ and
+ begin F=(fun () -> X=20 end), F(), true end
+ and
+ begin X=15, true end andalso X.
+
+parallel_scopes_2c(A, B) ->
+ begin X=A, true end
+ and
+ begin F = (fun () -> X = make_ref() end), F(), true end
+ and
+ begin X=B, true end andalso X.
+
+parallel_scopes_2d(A, B) ->
+ begin X=A, true end
+ and
+ begin F = (fun () -> X = make_ref() end), F(), true end
+ and
+ begin X=B, true end andalso (X = A).
+
+parallel_scopes_2e(A, B) ->
+ begin X = {a,A}, true end
+ and
+ begin F=(fun () -> X = 20 end), F(), true end
+ and
+ begin X = {a,B}, true end andalso X.
+
+parallel_scopes_3(A) ->
+ L = [X = id(42),
+ fun() -> X = 2 end()],
+ {L,X,A}.
+
+parallel_scopes_4a(A, B) ->
+ 4 = length([X = A,
+ fun() -> X = 2 end(),
+ X = B,
+ fun() -> X = 2 end()]),
+ X.
+
+parallel_scopes_4b(A, B) ->
+ 4 = length([X = A,
+ case id(true) of
+ true ->
+ fun() -> X = 2 end()
+ end,
+ X = B,
+ case id(false) of
+ false ->
+ fun() -> X = 2 end()
+ end]),
+ X.
+
+parallel_scopes_4c(A, B) ->
+ [X = A,
+ fun() -> X = 2 end(),
+ X = B,
+ fun() -> X = 2 end()].
+
+parallel_scopes_5(A, B) ->
+ 4 = length([X = A,
+ [fun() -> X = 2 end()],
+ X = B |
+ case id(false) of
+ false ->
+ [fun() -> X = 2 end()]
+ end]),
+ X.
+
+parallel_scopes_6(A, B) ->
+ 4 = tuple_size({X = A,
+ fun() -> X = 40 end(),
+ X = B,
+ fun() -> X = 50 end()}),
+ X.
+
+parallel_scopes_7(A, B) ->
+ 4 = tuple_size({X = A,
+ [fun() -> X = 40 end()],
+ X = B,
+ [fun() -> X = 50 end()]}),
+ X.
+
+parallel_scopes_8(A, B) ->
+ _ = [X = id(A),
+ begin fun() -> X = 2 end(), X = id(B) end],
+ X.
+
+parallel_scopes_9(A, B, C) ->
+ 3 = length([begin X = id(A), Y = id(A+B) end,
+ fun() -> X = 2 end(),
+ X = id(B)]),
+ {X,Y=C}.
+
+parallel_scopes_10(Bool, A, B, C) ->
+ T = {X = A,
+ case id(Bool) of
+ true ->
+ fun() -> X = 999 end(),
+ X = B;
+ false ->
+ X = C,
+ fun() -> X = 999 end()
+ end},
+ 2 = tuple_size(T),
+ X.
+
+parallel_scopes_11(A, B, C) ->
+ T = {X = A,
+ case id(true) of
+ true ->
+ X = B,
+ 2 = length([X = C, X = C]),
+ fun() -> X = 889 end();
+ false ->
+ X = cannot_happen
+ end},
+ 2 = tuple_size(T),
+ X.
+
+parallel_scopes_12(A, B, C) ->
+ T = {X = A,
+ case id(true) of
+ true ->
+ fun() -> X = whatever end(),
+ 2 = length([X = B, X = B]),
+ X = C;
+ false ->
+ X = cannot_happen
+ end},
+ 2 = tuple_size(T),
+ X.
+
+parallel_scopes_13(A, B) ->
+ [X = 1,
+ fun() ->
+ X = id(whatever),
+ 99 = A,
+ 100 = B
+ end()].
+
id(I) ->
I.
--
2.31.1