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

Expand symbolic funs correctly #9246

Merged
merged 3 commits into from
Jan 15, 2025
Merged

Conversation

richcarl
Copy link
Contributor

If a fun f/n used the same name and arity f/n as a BIF erlang:f/n, for example max/2, the symbolic fun would get unnecessarily rewritten as fun (X1, ..., Xn) -> f(X1, ..., Xn) end.

In addition, the rewritten fun body was then processed as a normal call, so if max/2 was declared as an import from some module m, it got expanded to a call to m:max/2, making imports "work" for symbolic funs but only if their names look like BIF names in order to sneak past the linter (albeit with a warning).

This change makes the expander do the right thing for all imports, auto or explicit, creating symbolic remote funs, and leaves to the linter to decide what is allowed. In the linter, it avoids marking f/n as a used local if it is listed as an import, instead reporting an error if you have a fun f/n that refers to an explicit import, and furthermore checks the reference f/n just like a call f(X1, ... Xn) so that we get the same warnings for deprecated functions etc., for example for fun now/0.

If desired, one could simply drop the check for imports in fun f/n, making the behaviour uniform whether the function is auto-imported or explicitly imported. That rule seems rather arbitrary.

If a 'fun f/n' used the same name and arity f/n as a BIF 'erlang:f/n',
for example 'max/2', the symbolic fun would get unnecessarily
rewritten as 'fun (X1, ..., Xn) -> f(X1, ..., Xn) end'.

In addition, the rewritten fun body was then processed as a normal
call, so if 'max/2' was declared as an import from some module m, it
got expanded to a call to 'm:max/2', making imports "work" for
symbolic funs but only if their names look like BIF names in order to
sneak past the linter.

This change makes the expander do the right thing for all imports,
auto or explicit, creating symbolic remote funs. It is up to the
linter (standard or modified) to decide whether it is allowed.
Copy link
Contributor

github-actions bot commented Dec 25, 2024

CT Test Results

    4 files    448 suites   1h 25m 50s ⏱️
3 238 tests 3 192 ✅ 46 💤 0 ❌
8 469 runs  8 420 ✅ 49 💤 0 ❌

Results for commit e88ee26.

♻️ 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

@IngelaAndin IngelaAndin added the team:VM Assigned to OTP team VM label Jan 3, 2025
@bjorng bjorng self-assigned this Jan 7, 2025
@bjorng bjorng added testing currently being tested, tag is used by OTP internal CI fix and removed testing currently being tested, tag is used by OTP internal CI labels Jan 7, 2025
@bjorng
Copy link
Contributor

bjorng commented Jan 7, 2025

If I update the primary bootstrap, the build fails:

erl_types.erl:579:20: ambiguous call of overridden pre Erlang/OTP R14 auto-imported BIF is_boolean/1 --
use erlang:is_boolean/1 or \"-compile({no_auto_import,[is_boolean/1]}).\" to resolve name clash
%  579|   structural(Type, fun is_boolean/1).
%     |                    ^

erl_types.erl:612:22: ambiguous call of overridden pre Erlang/OTP R14 auto-imported BIF is_binary/1 --
use erlang:is_binary/1 or \"-compile({no_auto_import,[is_binary/1]}).\" to resolve name clash
%  612|     structural(Type, fun is_binary/1).
%     |                      ^

erl_types.erl:821:20: ambiguous call of overridden pre Erlang/OTP R14 auto-imported BIF is_number/1 --
use erlang:is_number/1 or \"-compile({no_auto_import,[is_number/1]}).\" to resolve name clash
%  821|   structural(Type, fun is_number/1).
%     |                    ^

(If I don't update the primary bootstrap, Dialyzer fails instead: https://github.com/erlang/otp/actions/runs/12495954151/job/34867261463?pr=9246)

@richcarl
Copy link
Contributor Author

richcarl commented Jan 9, 2025

erl_types.erl:579:20: ambiguous call of overridden pre Erlang/OTP R14 auto-imported BIF is_boolean/1 --
use erlang:is_boolean/1 or "-compile({no_auto_import,[is_boolean/1]})." to resolve name clash
% 579| structural(Type, fun is_boolean/1).

I see. It's the check I added:

furthermore checks the reference f/n just like a call f(X1, ... Xn) so that we get the same warnings for deprecated functions etc.

Since funs have never been checked for this before (which should be considered a bug), I'm not quite sure what to do here. Making existing code not compile, like the erl_types module in dialyzer, is obviously not nice, but if it's a compiler bug the code shouldn't have been legal to begin with. I.e., erl_types defines and exports a local function is_boolean(X) -> ..., and if there had been a local call B = is_boolean(X) in erl_types without declaring -compile({no_auto_import,[is_boolean/1]})., it would have been an error. But currently the only use is as fun is_boolean/1, which was not checked.

Specifically, these things only become errors for functions f/a which are "old BIFs" (pre-R14) according to erl_internal:old_bif/2. For other auto-imports it's just a warning. It's not clear to me if these old-bif checks still serve a purpose.

@bjorng
Copy link
Contributor

bjorng commented Jan 9, 2025

It's not clear to me if these old-bif checks still serve a purpose.

We just had a long discussion and decided that they no longer serve any useful purpose. That is, there is no longer any need to check whether the BIF is old (and erl_internal:old_bif/2 can be removed), and the same warning is generated for all BIFs. Feel free to do that change.

@richcarl richcarl force-pushed the expand-symbolic-fun branch from 61b5873 to 9da3284 Compare January 9, 2025 21:02
@bjorng bjorng added the testing currently being tested, tag is used by OTP internal CI label Jan 10, 2025
Copy link
Contributor

@bjorng bjorng left a comment

Choose a reason for hiding this comment

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

The test case error_SUITE:bif_clashes/1 for the compiler fails:

4:18: ambiguous call of overridden auto-imported BIF length/1 --
use erlang:length/1 or "-compile({no_auto_import,[length/1]})." to resolve name clash
                 length([a,b,c]).
                 ^

Test bif_clashes1 failed. Expected
  {error,[{{4,18},erl_lint,{call_to_redefined_old_bif,{length,1}}}],[]}
but got
  {warning,[{{4,18},erl_lint,{call_to_redefined_bif,{length,1}}}]}

Otherwise it looks good to me.

@bjorng bjorng added testing currently being tested, tag is used by OTP internal CI and removed testing currently being tested, tag is used by OTP internal CI labels Jan 13, 2025
@bjorng
Copy link
Contributor

bjorng commented Jan 15, 2025

Could you do some squashing of the commits to avoid problems if we'll need to do a git bisect? At least I think that the last two commits should be combined, and that the dialyzer commit should be combined with the previous commit.

The linting of 'fun f/n' relied on marking f/n as used and later
getting a report if function f/n did not exist, except if f/n was not
in the list of local functions (could still be local but auto
generated) and was listed in auto-imports (erlang:f/n) and the import
was not suppressed. This allowed explicit imports of names such as
'max/2' to slip through with a warning, assuming that they were
auto-imports, and the expander would then expand them like an explicit
import.

This change avoids marking f/n as a used local if it is listed as an
import, makes it an error to have a 'fun f/n' that refers to an
explicit import, and furthermore checks the reference f/n just like a
call f(X1, ... Xn) so that we get the same warnings for deprecated
functions etc., for example 'fun now/0'.
This makes uses of local functions whose names conflict with auto-imported
BIFs (without a no_auto_import declaration to resolve the conflict) always
be a warning, never an error. Previously, this was an error for "old"
BIfs (those that were auto imported pre OTP R14).
@richcarl richcarl force-pushed the expand-symbolic-fun branch from 8527ccf to e88ee26 Compare January 15, 2025 08:11
@richcarl
Copy link
Contributor Author

Squashed.

@bjorng bjorng merged commit 6e3b85b into erlang:master Jan 15, 2025
26 checks passed
@bjorng
Copy link
Contributor

bjorng commented Jan 15, 2025

Thanks for your pull request.

@richcarl richcarl deleted the expand-symbolic-fun branch January 15, 2025 10:07
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 testing currently being tested, tag is used by OTP internal CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants