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 4 commits into
base: master
Choose a base branch
from

Conversation

frazze-jobb
Copy link
Contributor

records that have field default values containing variables that are "free" was unsafe in functions that have variables with the same name. This commit creates init function for records to protect the variables in the default value.

e.g.
-record(r, {f = fun(X)->case X of {y, Y} -> Y; _ -> X end, g=..., h=abc}). foo(X)->#r{}. --> foo(X)->(r_init()){}.

r_init() will only initialize fields that will not be updated e.g.
foo(X)->#r{f=X} --> foo(X)->(r_init_f()){f=X}.
r_init_f will only initialize g and h with its default value, f will be initialized to undefined.

r_init() functions will not be generated if all fields of the record that contains "free variables" are initialized by the user.
e.g.
foo(X)->#r{f=X,g=X}. --> foo(X)->{r,X,X,abc}.

closes #9317

@frazze-jobb frazze-jobb self-assigned this Feb 3, 2025
Copy link
Contributor

github-actions bot commented Feb 3, 2025

CT Test Results

    2 files     97 suites   1h 8m 58s ⏱️
2 190 tests 2 142 ✅ 47 💤 1 ❌
2 556 runs  2 506 ✅ 49 💤 1 ❌

For more details on these failures, see this check.

Results for commit 5e9f9b8.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@frazze-jobb frazze-jobb added team:VM Assigned to OTP team VM fix labels Feb 3, 2025
@frazze-jobb frazze-jobb force-pushed the frazze/stdlib/erl_expand_records_create_init_function/OTP-19464 branch from d544c98 to e4b004f Compare February 3, 2025 11:31
lib/stdlib/src/erl_expand_records.erl Outdated Show resolved Hide resolved
lib/stdlib/src/erl_expand_records.erl Show resolved Hide resolved
St);

IsUndefined = [{RF, AnnoRF, Field, {atom, AnnoRF, 'undefined'}} || {record_field=RF, AnnoRF, Field, _} <- Is],
Fields = lists:flatten(lists:sort([atom_to_list(FieldAtom) || {record_field, _, {atom, _, FieldAtom}, _} <- Is])),
Copy link
Contributor

Choose a reason for hiding this comment

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

In general, prefer binaries over lists, and to sink expressions as far down as possible: the compiler has to maintain observability with regards to tracing, and cannot sink this expression down to the true branch below as that would result in a different trace.

@ilya-klyuchnikov
Copy link
Contributor

Now, as "free vars" are allowed in record fields, these free vars can affect each other.

-module(z).

-export([mk1/0]).

-record(a, {
  a = X = id(1),
  b = X = id(2)
}).

id(X) -> X.

mk1() ->
  #a{}.
1> z:mk1().
** exception error: no match of right hand side value 2
     in function  z:'rec_init$^0'/0 (z.erl, line 13)

@frazze-jobb
Copy link
Contributor Author

Now, as "free vars" are allowed in record fields, these free vars can affect each other.

Thanks, I'll get to it soon!

records that have field default values containing variables that are "free"
was unsafe in functions that have variables with the same name.
This commit creates init function for records to protect the variables in the
default value.

e.g.
-record(r, {f = fun(X)->case X of {y, Y} -> Y; _ -> X end, g=..., h=abc}).
foo(X)->\#r{}. --> foo(X)->(r_init()){}.

r_init() will only initialize fields that will not be updated
e.g.
foo(X)->\#r{f=X} --> foo(X)->(r_init_f()){f=X}.
r_init_f will only initialize g and h with its default value, f will be initialized
to undefined.

r_init() functions will not be generated if all fields of the record that contains
"free variables" are initialized by the user.
e.g.
foo(X)->\#r{f=X,g=X}. --> foo(X)->{r,X,X,abc}.
Add sequence number for init record functions
@frazze-jobb
Copy link
Contributor Author

Now, as "free vars" are allowed in record fields, these free vars can affect each other.

Thanks, I'll get to it soon!

Actually, I spoke to soon. This is as intended. Just like how it works if you try to update the record like this: #r0{a=X=id(1),b=X=id(2)} that would not work either.

But I will update the linter so that it warns for this.

@frazze-jobb frazze-jobb force-pushed the frazze/stdlib/erl_expand_records_create_init_function/OTP-19464 branch from 4ed5f0c to fc096c6 Compare February 4, 2025 13:11
@elbrujohalcon
Copy link
Contributor

elbrujohalcon commented Feb 4, 2025

Any chance of not including the new function in the error description?

So, from the other comment…

1> z:mk1().
** exception error: no match of right hand side value 2
     in function  z:'rec_init$^0'/0 (z.erl, line 13)

I would've preferred to see something like…

1> z:mk1().
** exception error: no match of right hand side value 2
     in function  z:mk1/0 (z.erl, line 13)

@frazze-jobb
Copy link
Contributor Author

Any chance of not including the new function in the error description?

Tail calls, like in this case, makes it invisible in the stacktrace.
Instead I suggest:
in record default value (z.erl, line 13)

Would that work for you?

@michalmuskala
Copy link
Contributor

Another option would be to explicitly prevent the compiler from tail calling into this helper function, and always emit a full call with building a stack frame

traverse_af(AF, Fun) ->
traverse_af(AF, Fun, []).
traverse_af(AF, Fun, Acc) when is_list(AF) ->
[ traverse_af(Ast, Fun, Fun(Ast,Acc)) || Ast <- AF];
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
[ traverse_af(Ast, Fun, Fun(Ast,Acc)) || Ast <- AF];
[traverse_af(Ast, Fun, Fun(Ast,Acc)) || Ast <- AF];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fix team:VM Assigned to OTP team VM
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Variables in default field values of records
6 participants