Preceding Statements#1116
Conversation
|
When calling a function with preceding statement arguments, the function itself and the I've made a PR to the source branch with (currently failing) tests for this: |
- removed optimization that broke certain situations - optimized out extra temp in common unary operator cases - removed duplicate functionality between transformOrderedExpressions and transformExpressionList
src/transformation/visitors/binary-expression/destructuring-assignments.ts
Outdated
Show resolved
Hide resolved
|
Wow, great work! First of all, sorry that I probably won't have the time to review this. |
|
@lolleko Consider this: function changeG(this: void) { _G = {}; return 1; };
foo(i += changeG());function changeG()
_G = {}
return 1
end
local ____foo_1 = foo
local ____G_0 = _G
i = i + changeG()
____foo_1(____G_0, i)
|
…f loop conditions
| for (let i = 0; i < expressions.length; ++i) { | ||
| context.pushPrecedingStatements(); | ||
| transformExpressions.push(context.transformExpression(expressions[i])); | ||
| const expressionPrecedingStatements = context.popPrecedingStatements(); |
There was a problem hiding this comment.
Guess you could still use the transformInPrecedingStatementScope helper here
|
Nullish coalescing is not implemented with preceding statements. This led me to see a larger problem: E.g. see this failing test: Assignment expressions with a table index lhs uses |
|
If I can comment further, in hindsight I think an issue with this preceding statements implementation is that it makes the code much more stateful; every transform now has the possibility of adding preceding statements, and so can easily lead bugs like above. An idea for a less stateful implementation would be to introduce some interface ExpressionAndPrecedingStatements (with a shorter name) which contains a lua.Expression and preceding statements needed when evaluated. (Most) parts of the code base that currently return Utilities such as This would touch more of the existing code, but might be worth it. Preceding statements already has an impact on almost all parts of transformation. I'm open to discuss this/alternatives |
Thanks for clarifying, I understand now. |
Good catch on this one. It's going to take some refactoring to get right. 😞
This was my original plan for this, but it's a fundamental change that would require refactoring pretty much all transformations, so I avoided it in the hopes of keeping the scope of this PR down. |
The scope of this PR is already pretty big... Even without refactoring everything, using this interface still may be beneficial. Double-checking every function that takes a lua.Expression but not preceding statements, |
Right now the changes to expressions are very limited, all logic for combining and forwarding preceding statements is now spread over the statement transformers, which is already more than I would ideally like. Doing this change would have a very large impact because all expression transformers would need to be touched, and all of them would require to have some logic about how to forward preceding statements from its child expressions. If we can make this PR change without doing this refactor, I would be in favor of that. I am interested in this alternative, but would prefer to judge it as an independent change. |
|
The issue with
Thanks for reminding me on this. I had meant to put in a bug for it but forgot. Since it's not directly related to preceding statements, I think it would be best to fix this in a follow up PR. |
| switchVariable: ts.Identifier, | ||
| expression: ts.Expression | ||
| ): ts.Expression => { | ||
| const comparison = ts.factory.createBinaryExpression( |
There was a problem hiding this comment.
This looks nice and simple, but don't all these ts.factory.create* methods interfere with source maps and potentially type checking that needs tree traversal?
There was a problem hiding this comment.
Good point. Wasn't fully considering that. I've reverted to using lua expressions here and manually handled preceding statements.
…ured ts expressions and handled preceding statements manually
|
To comment on the above, I believe you can use |



Closes #654
This PR fully replaces our IIFEs with preceding statements. To do this, it uses a stack based system akin to what is detailed out here: roblox-ts/roblox-ts#537. Statements can be added before the current expression being transformed using
context.addPrecedingStatements(). If nodes higher up want to catch and manipulate these statements, they can do so by pushing and popping a statement collection withcontext.pushPrecedingStatements()andcontext.popPrecedingStatements().Significant refactoring was required in a few places to pull this off. Call expression handling has been modified a lot, but should be understandable. Dealing with assignments was particularly tricky, though, as there's a complex web of cases (especially destructuring assignments). At some point I imagine a full rewrite of assignment handling might be needed to keep it understandable and maintainable, but I'm not even sure what that would look like right now 😅
Evaluation Order
The biggest challenge with this system is maintaining evaluation order in statements with multiple expressions. Consider this example:
Once
i += bar()is lifted into a preceding statement, any expressions before it in the statement could be affected, and thus need to be cached in temps ahead of time to maintain evaluation order.Unfortunately, without deeper inspection of the preceding statements being generated, we have to aggressively do this caching, even in cases which don't seem to need it upon visual inspection. For example:
Transpiles to:
Both
fooand even_Ghave to be cached in temps because we can't guarantee that the preceding statements generated by++iwon't modify them. It's possible we could add some specific optimizations for cases like this to reduce the temps, but it would be tricky and a bit messy. Also, thanks to operator overloading, it is possible++icould have actually modified those values.constvariables won't be cached like this however. Those looking to reduce temps should preferconstoverletwhenever possible.Sparse Array Lib
To help with situations which could generate extreme numbers of temps, this PR also adds some new lib functions for manipulating sparse arrays. Consider this:
Instead of caching all of those variables in temps, we can push them into an array while preserving evaluation order and unpack them into the call's arguments. But, since any of these could have nil values, we use a custom array that tracks its true size.
This technique can also be used to handle spread expressions in the middle of arguments, so it also fixes #1142 as a bonus.
Right now, argument lists and array expressions which would generate 3 or more temps when transformed will revert to this behavior. That number can be adjusted, if needed.
Other Notes