diff options
author | Björn Gustavsson <bjorn@erlang.org> | 2022-04-28 07:03:13 +0200 |
---|---|---|
committer | Björn Gustavsson <bjorn@erlang.org> | 2022-04-28 08:26:14 +0200 |
commit | ffb60e3537d65e7c34c8e50f86572be8dc42acc4 (patch) | |
tree | 3a8705f9d151256ec39328a286dbb7b4e5e4a273 /lib | |
parent | 61c4f8ede7d9b15b6f7f5dcadd6127c8d56e3e35 (diff) | |
download | erlang-ffb60e3537d65e7c34c8e50f86572be8dc42acc4.tar.gz |
Fix multi-init record initialization edge case
The compiler would crash when attempting to compile the
following code:
-record(r, {a}).
foo() ->
R = #r{a = [], _ = V = 42},
{R,V}.
The reason is that when all fields in the record had been explicitly
initialized, the `erl_expands_records` pass would rewrite the code
to:
foo() ->
R = {[]},
{R,V}.
This commit eliminates that bug by updating `erl_expand_records`
to ensure that the initializing expression is still executed even
when all records fields are explicitly initialized:
foo() ->
begin
V = 42,
R = {[]}
end,
{R,V}.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/compiler/test/record_SUITE.erl | 20 | ||||
-rw-r--r-- | lib/stdlib/src/erl_expand_records.erl | 58 | ||||
-rw-r--r-- | lib/stdlib/test/erl_expand_records_SUITE.erl | 13 |
3 files changed, 78 insertions, 13 deletions
diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl index cd60525691..d3bc2d2018 100644 --- a/lib/compiler/test/record_SUITE.erl +++ b/lib/compiler/test/record_SUITE.erl @@ -28,7 +28,8 @@ init_per_testcase/2,end_per_testcase/2, errors/1,record_test_2/1,record_test_3/1,record_access_in_guards/1, guard_opt/1,eval_once/1,foobar/1,missing_test_heap/1, - nested_access/1,coverage/1,grab_bag/1,slow_compilation/1]). + nested_access/1,coverage/1,grab_bag/1,slow_compilation/1, + wildcard_init/1]). init_per_testcase(_Case, Config) -> Config. @@ -48,7 +49,7 @@ groups() -> [errors,record_test_2,record_test_3, record_access_in_guards,guard_opt,eval_once,foobar, missing_test_heap,nested_access,coverage,grab_bag, - slow_compilation]}]. + slow_compilation,wildcard_init]}]. init_per_suite(Config) -> @@ -745,6 +746,21 @@ slow_compilation(Config) when is_list(Config) -> {f56,R#slow_r.f56},{f57,R#slow_r.f57},{f58,R#slow_r.f58}, {f59,R#slow_r.f59}]. +wildcard_init(_Config) -> + {42,#foo{a=1,b=2,c=3,d=4}} = wc_init_1(), + error = wc_init_2(), + ok. + +wc_init_1() -> + R = #foo{a=1,b=2,c=3,d=4,_=V=42}, + {V,R}. + +wc_init_2() when #foo{a=1,b=2,c=3,d=4,_=42} -> ok; +wc_init_2() -> error. + + first_arg(First, _) -> First. + + id(I) -> I. diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 5a720b00f3..a42fdedef0 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -310,9 +310,28 @@ expr({record_index,Anno,Name,F}, St) -> expr(I, St); expr({record,Anno0,Name,Is}, St) -> Anno = mark_record(Anno0, St), - expr({tuple,Anno,[{atom,Anno0,Name} | - record_inits(record_fields(Name, Anno0, St), Is)]}, - St); + case record_inits(record_fields(Name, Anno0, St), Is) of + {Inits,none} -> + %% There is no wildcard init or it was used at + %% least once. + expr({tuple,Anno,[{atom,Anno0,Name}|Inits]}, St); + {Inits,WcInits} -> + %% There is a wildcard init that was never used + %% because all fields had their own explicit inits. + %% Be sure to use the wildcard init in case it + %% binds any variables. Example: + %% + %% -record(r, {a}). + %% foo() -> + %% R = #r{a = [], _ = V = 42}, + %% {R,V}. + %% + EmptyAnno = no_compiler_warning(erl_anno:new(0)), + Block = {block,EmptyAnno, + [WcInits, + {tuple,Anno,[{atom,Anno0,Name}|Inits]}]}, + expr(Block, St) + end; expr({record_field,_A,R,Name,F}, St) -> Anno = erl_parse:first_anno(R), get_record_field(Anno, R, F, Name, St); @@ -664,20 +683,37 @@ pattern_fields(Fs, Ms) -> end end, Fs). -%% record_inits([RecDefField], [Init]) -> [InitExpr]. +%% record_inits([RecDefField], [Init]) -> {[InitExpr],WildcardInit}. %% Build a list of initialisation expressions for the record tuple %% elements. This expansion must be passed through expr %% again. N.B. We are scanning the record definition field list! record_inits(Fs, Is) -> WildcardInit = record_wildcard_init(Is), - map(fun ({record_field,_,{atom,_,F},D}) -> - case find_field(F, Is) of - {ok,Init} -> Init; - error when WildcardInit =:= none -> D; - error -> WildcardInit - end - end, Fs). + record_inits_1(Fs, Is, WildcardInit, false, []). + +record_inits_1([{record_field,_,{atom,_,F},Def}|Fs], + Is, WcInit, WcUsed, Acc) -> + case find_field(F, Is) of + {ok,Init} -> + record_inits_1(Fs, Is, WcInit, WcUsed, [Init|Acc]); + error when WcInit =:= none -> + record_inits_1(Fs, Is, WcInit, WcUsed, [Def|Acc]); + error -> + record_inits_1(Fs, Is, WcInit, true, [WcInit|Acc]) + end; +record_inits_1([], _Is, WcInit0, WcUsed, Acc) -> + WcInit = case {WcUsed,is_in_guard()} of + {false,false} -> + %% This code is in a body and the wildcard init + %% expression (if any) was never used. + WcInit0; + {_,_} -> + %% The wildcard init was either used at least + %% once or this code is in a guard. + none + end, + {reverse(Acc),WcInit}. record_wildcard_init([{record_field,_,{var,_,'_'},D} | _]) -> D; record_wildcard_init([_ | Is]) -> record_wildcard_init(Is); diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl index c48a1ac90e..fdae6ce284 100644 --- a/lib/stdlib/test/erl_expand_records_SUITE.erl +++ b/lib/stdlib/test/erl_expand_records_SUITE.erl @@ -217,6 +217,19 @@ init(Config) when is_list(Config) -> #r{b = b, _ = init} -> ok; _ -> not_ok end. + ">>, + <<"-record(r, {a}). + t() -> + {42,#r{a=[]}} = foo(), + error = bar(), + ok. + + foo() -> + R = #r{a = [], _ = V = 42}, + {V,R}. + + bar() when #r{a = [], _ = 42} -> ok; + bar() -> error. ">> ], run(Config, Ts), |