Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stdlib: create an init function for records with complex default values #9373

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions lib/stdlib/src/edlin_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,8 @@ is_type(Type, Cs, String) ->
catch
_:_ ->
%% Types not possible to deduce with erl_parse
% If string contains variables, erl_parse:parse_term will fail, but we
% consider them valid sooo.. lets replace them with the atom var
%% If string contains variables, erl_parse:parse_term will fail, but we
%% consider them valid sooo.. lets replace them with the atom var
B = [(fun({var, Anno, _}) -> {atom, Anno, var}; (Token) -> Token end)(X) || X <- A],
try
{ok, Term2} = erl_parse:parse_term(B),
Expand Down Expand Up @@ -729,30 +729,34 @@ expand_filepath(PathPrefix, Word) ->
X -> X
end.

shell(Fun) ->
{ok, [{atom, _, Fun1}], _} = erl_scan:string(Fun),
case shell:local_func(Fun1) of
shell(Fun) when is_atom(Fun) ->
case shell:local_func(Fun) of
true -> "shell";
false -> "user_defined"
end.

-doc false.
shell_default_or_bif(Fun) when is_atom(Fun) ->
case lists:member(Fun, [E || {E,_}<-get_exports(shell_default)]) of
true -> "shell_default";
false -> bif(Fun)
end;
shell_default_or_bif(Fun) ->
case erl_scan:string(Fun) of
{ok, [{var, _, _}], _} -> [];
{ok, [{atom, _, Fun1}], _} ->
case lists:member(Fun1, [E || {E,_}<-get_exports(shell_default)]) of
true -> "shell_default";
_ -> bif(Fun)
end
{ok, [{atom, _, Fun1}], _} -> shell_default_or_bif(Fun1);
_ -> []
end.

-doc false.
bif(Fun) ->
{ok, [{atom, _, Fun1}], _} = erl_scan:string(Fun),
case lists:member(Fun1, [E || {E,A}<-get_exports(erlang), erl_internal:bif(E,A)]) of
bif(Fun) when is_atom(Fun) ->
case lists:member(Fun, [E || {E,_}<-get_exports(erlang)]) of
true -> "erlang";
_ -> shell(Fun)
false -> shell(Fun)
end;
bif(Fun) ->
case erl_scan:string(Fun) of
{ok, [{atom, _, Fun1}], _} -> bif(Fun1);
_ -> []
end.

expand_string(Bef0) ->
Expand Down
20 changes: 18 additions & 2 deletions lib/stdlib/src/erl_error.erl
Original file line number Diff line number Diff line change
Expand Up @@ -536,10 +536,20 @@ location(L) ->
sep(1, S) -> S;
sep(_, S) -> [$\n | S].

is_rec_init(F) when is_atom(F) ->
case atom_to_binary(F) of
<<"rec_init$^", _/binary>> -> true;
_ -> false
end;
is_rec_init(_) -> false.

origin(1, M, F, A) ->
case is_op({M, F}, n_args(A)) of
{yes, F} -> <<"in operator ">>;
no -> <<"in function ">>
no -> case is_rec_init(F) of
true -> <<"in record">>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any test of this added code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes a testcase has been added

false -> <<"in function ">>
end
end;
origin(_N, _M, _F, _A) ->
<<"in call from">>.
Expand Down Expand Up @@ -625,7 +635,13 @@ printable_list(_, As) ->
io_lib:printable_list(As).

mfa_to_string(M, F, A, Enc) ->
io_lib:fwrite(<<"~ts/~w">>, [mf_to_string({M, F}, A, Enc), A]).
case is_rec_init(F) of
true ->
<<"default value">>;
false ->
io_lib:fwrite(<<"~ts/~w">>,
[mf_to_string({M, F}, A, Enc), A])
end.

mf_to_string({M, F}, A, Enc) ->
case erl_internal:bif(M, F, A) of
Expand Down
76 changes: 71 additions & 5 deletions lib/stdlib/src/erl_expand_records.erl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Section [The Abstract Format](`e:erts:absform.md`) in ERTS User's Guide.
strict_ra=[], % Strict record accesses
checked_ra=[], % Successfully accessed records
dialyzer=false, % Compiler option 'dialyzer'
rec_init_count=0, % Number of generated record init functions
new_forms=#{}, % New forms
strict_rec_tests=true :: boolean()
}).

Expand Down Expand Up @@ -95,6 +97,12 @@ forms([{function,Anno,N,A,Cs0} | Fs0], St0) ->
forms([F | Fs0], St0) ->
{Fs,St} = forms(Fs0, St0),
{[F | Fs], St};
forms([], #exprec{new_forms=FsN}=St) ->
{[{'function', Anno,
maps:get(Def, FsN),
0,
[{'clause', Anno, [], [], [Def]}]}
|| {_,Anno,_}=Def <- maps:keys(FsN)], St};
forms([], St) -> {[],St}.

clauses([{clause,Anno,H0,G0,B0} | Cs0], St0) ->
Expand Down Expand Up @@ -262,6 +270,30 @@ not_a_tuple({op,_,_,_}) -> true;
not_a_tuple({op,_,_,_,_}) -> true;
not_a_tuple(_) -> false.

variables({var,_,'_'}) ->
[];
variables({var,_,V}) ->
[V];
variables({'fun',_,Def}) ->
%% The Def tuple has no annotation. Must handle it specially.
case Def of
{clauses,Cs} -> variables(Cs);
{function,F,A} -> variables([F,A]);
{function,M,F,A} -> variables([M,F,A])
end;
variables(Tuple) when is_tuple(Tuple) ->
[Tag,Anno|T] = tuple_to_list(Tuple),
true = is_atom(Tag),
true = erl_anno:is_anno(Anno),
variables(T);
variables(List) when is_list(List) ->
foldl(fun(E, Vs0) ->
Vs1 = variables(E),
ordsets:union(Vs0, Vs1)
end, [], List);
variables(_) ->
[].

record_test_in_body(Anno, Expr, Name, St0) ->
%% As Expr may have side effects, we must evaluate it
%% first and bind the value to a new variable.
Expand Down Expand Up @@ -333,11 +365,45 @@ expr({map_field_exact,Anno,K0,V0}, St0) ->
expr({record_index,Anno,Name,F}, St) ->
I = index_expr(Anno, F, Name, record_fields(Name, Anno, 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);
expr({record,Anno0,Name,Is}, St0) ->
Anno = mark_record(Anno0, St0),

RInit = [{atom,Anno,Name} |
record_inits(record_fields(Name, Anno0, St0), Is)],
Vars = variables(Is),
%% Check if there are variables in the initialized record. If
%% there are, we need to initialize the record using a generated
%% function
AnyVariables = not ordsets:is_subset(variables(RInit), Vars),
case AnyVariables of
true ->
%% Initialize the record with only the default values.
%% Setting fields that has been overridden to undefined.
UndefIs = [setelement(4,R,{atom,Anno,undefined}) || {record_field,_,_,_}=R<-Is],
RDefInit = [{atom,Anno,Name} |
record_inits(record_fields(Name, Anno, St0), UndefIs)],
{Def,St1} = expr({tuple,Anno,RDefInit}, St0),
Map0 = St1#exprec.new_forms,
{FName,St2} =
case Map0 of
#{Def := OldName} ->
{OldName,St1};
#{} ->
C = St1#exprec.rec_init_count,
NewName = list_to_atom("rec_init$^" ++
integer_to_list(C)),
Map = Map0#{Def => NewName},
{NewName,St1#exprec{rec_init_count=C+1,
new_forms=Map}}
end,
%% Replace the init record expression with a call expression
%% to the newly added function followed by a record update.
expr({record, Anno0, {call,Anno,{atom,Anno,FName},[]}, Name, Is},St2);
false ->
%% No free variables means that we can just output the
%% record as a tuple.
expr({tuple,Anno,RInit}, St0)
end;
expr({record_field,_A,R,Name,F}, St) ->
Anno = erl_parse:first_anno(R),
get_record_field(Anno, R, F, Name, St);
Expand Down
30 changes: 6 additions & 24 deletions lib/stdlib/src/erl_lint.erl
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,6 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
errors=[] :: [{file:filename(),error_info()}], %Current errors
warnings=[] :: [{file:filename(),error_info()}], %Current warnings
file = "" :: string(), %From last file attribute
recdef_top=false :: boolean(), %true in record initialisation
%outside any fun or lc
xqlc= false :: boolean(), %true if qlc.hrl included
called= [] :: [{fa(),anno()}], %Called functions
fun_used_vars = undefined %Funs used vars
Expand Down Expand Up @@ -469,8 +467,6 @@ format_error_1({shadowed_var,V,In}) ->
{~"variable ~w shadowed in ~w", [V,In]};
format_error_1({unused_var, V}) ->
{~"variable ~w is unused", [V]};
format_error_1({variable_in_record_def,V}) ->
{~"variable ~w in record definition", [V]};
format_error_1({stacktrace_guard,V}) ->
{~"stacktrace variable ~w must not be used in a guard", [V]};
format_error_1({stacktrace_bound,V}) ->
Expand Down Expand Up @@ -3090,21 +3086,14 @@ def_fields(Fs0, Name, St0) ->
case exist_field(F, Fs) of
true -> {Fs,add_error(Af, {redefine_field,Name,F}, St)};
false ->
St1 = St#lint{recdef_top = true},
{_,St2} = expr(V, [], St1),
%% Warnings and errors found are kept, but
%% updated calls, records, etc. are discarded.
St3 = St1#lint{warnings = St2#lint.warnings,
errors = St2#lint.errors,
called = St2#lint.called,
recdef_top = false},
{_,St1} = expr(V, [], St),
%% This is one way of avoiding a loop for
%% "recursive" definitions.
NV = case St2#lint.errors =:= St1#lint.errors of
NV = case St1#lint.errors =:= St#lint.errors of
true -> V;
false -> {atom,Aa,undefined}
end,
{[{record_field,Af,{atom,Aa,F},NV}|Fs],St3}
{[{record_field,Af,{atom,Aa,F},NV}|Fs],St1}
end
end, {[],St0}, Fs0).

Expand Down Expand Up @@ -4067,10 +4056,7 @@ comprehension_expr(E, Vt, St) ->
%% in ShadowVarTable (these are local variables that are not global variables).

lc_quals(Qs, Vt0, St0) ->
OldRecDef = St0#lint.recdef_top,
{Vt,Uvt,St} = lc_quals(Qs, Vt0, [], St0#lint{recdef_top = false}),
{Vt,Uvt,St#lint{recdef_top = OldRecDef}}.

lc_quals(Qs, Vt0, [], St0).
lc_quals([{zip,_Anno,Gens} | Qs], Vt0, Uvt0, St0) ->
St1 = are_all_generators(Gens,St0),
{Vt,Uvt,St} = handle_generators(Gens,Vt0,Uvt0,St1),
Expand Down Expand Up @@ -4205,13 +4191,12 @@ fun_clauses(Cs, Vt, St) ->
fun_clauses1(Cs, Vt, St).

fun_clauses1(Cs, Vt, St) ->
OldRecDef = St#lint.recdef_top,
{Bvt,St2} = foldl(fun (C, {Bvt0, St0}) ->
{Cvt,St1} = fun_clause(C, Vt, St0),
{vtmerge(Cvt, Bvt0),St1}
end, {[],St#lint{recdef_top = false}}, Cs),
end, {[],St}, Cs),
Uvt = vt_no_unsafe(vt_no_unused(vtold(Bvt, Vt))),
{Uvt,St2#lint{recdef_top = OldRecDef}}.
{Uvt,St2}.

fun_clause({clause,_Anno,H,G,B}, Vt0, St0) ->
{Hvt,Hnew,St1} = head(H, Vt0, [], St0), % No imported pattern variables
Expand Down Expand Up @@ -4289,9 +4274,6 @@ pat_var(V, Anno, Vt, New, St0) ->
{[{V,{bound,used,Ls}}],[],
%% As this is matching, exported vars are risky.
add_warning(Anno, {exported_var,V,From}, St)};
error when St0#lint.recdef_top ->
{[],[{V,{bound,unused,[Anno]}}],
add_error(Anno, {variable_in_record_def,V}, St0)};
error ->
%% add variable to NewVars, not yet used
{[],[{V,{bound,unused,[Anno]}}],St0}
Expand Down
Loading
Loading