-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Warn about variable exports from subexpressions #9134
base: master
Are you sure you want to change the base?
Conversation
There is never a good reason for placing variable bindings inside the arguments of an expression.
CT Test Results 13 files 245 suites 4h 9m 11s ⏱️ Results for commit 3e0a184. ♻️ 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 |
See #9153 for a way of allowing the use of |
Unfortunately this introduces issues to code generation:
Luckily, there is a simple solution to both problems above, which is to simply wrap each argument into a block expression or a That said, I'd say this should not really be the job of the compiler, it should be the job of a linter, especially because Erlang does not distinguish between statements and expressions, so expressions should be allowed anywhere as possible. At best, leave it as an opt-in/opt-out warning. FWIW, if I could change one thing about this feature is to make the variable defined in a previous argument always visible. This code warns because A won't ever match:
But this code does not compile:
I think I would rather prefer if A was always available for consistency. And I think it would make the Elixir compiler simpler but I am not sure if that's true for Erlang. |
@josevalim Note that you would only get a warning if there is an actual use of the variable outside the subexpression context where it is defined, as in |
I think that would be rare but it can still happen. My biggest concern is that macros and parse transforms may traverse AST to perform substitutions and transformations. Except for patterns/guards, they don't really need to worry about placement, this pull request would change that potentially pushing corner cases or harder to understand errors. |
It's not that A is available in the second argument in the first example, it's that both argument subexpressions define and export the same new variable (neither subexpression depends on the other), and then a match assertion gets inserted to ensure that they are the same value. It is unlikely that anyone does this sort of thing in practice, but it could happen by mistake, and the compiler must ensure that the variable has a consistent value, not randomly choosing either side. If exporting from subexpressions was dropped, there would be no need for that assertion. Instinctively I don't like the idea of starting to pass on the environment from left to right inside the argument list; it feels like it will just cause more complications. |
Thank you, that's a very good interpretation. I assume if Erlang were to introduce a pin operator, it would have to track the variables from before the call? e.g. |
Right now, both arguments are already evaluated in the environment as it is before the call. E.g. In general, I think it's best if arguments are always independent subexpressions and can be reordered at will without having to think about variables being defined on the right but not on the left. It would mean that for a simple refactoring you'd have to also move definitions out of one subexpression and into the leftmost one that needs it. |
Except for "block structures" like case, receive, etc., there is really no good reason to allow variable bindings to be exported from a subexpression. It is very rarely done in practice, and the resulting code is always confusing, having a distinct C flavour. For example:
instead of
At the same time, the requirement that the compiler must handle the exporting of variable bindings from any and every subexpression is an unnecessary tax on all compiler code and development tooling, which has to trace these possible bindings correctly. Doing this is complicated and error prone. It would be good if such exports could be phased out completely.
This patch adds source information to variables exported from non-block subexpressions, such as arguments to calls, tuples, etc., and adds an unconditional warning (irrespective of the
warn_export_vars
compiler flag) when such exports are used. It also fixes all such instances in the OTP code (about 20 files in the entire code base - looks like it's been done by a few individual authors only).