Add `remove_if_expression` rule
Add remove_if_expression rule which removes luau's if expression syntax from source then translate it into closure and if statement.
Tests are succeeded.
Examples
Input
local a = if true then 1 else 2
Output
local a = (true and {(1)} or {(2)})[1]
Thank for contributing! I think we could switch the replacement strategy though. Someone in the Roblox open source discord shared this trick where you can do:
local a = if condition() then 1 else 0 local a = (condition() and { 1 } or { 0 })[1]We should also wrap potential expressions that can return multiple values to avoid creating a bigger table than needed. So
...and functions calls should be wrapped in parentheses!
Hello! Thanks for reviewing! I read your all 3 comments which were really helpful for me to improve my contribution.
but sorry. one thing that I'm not quite understanding is "We should also wrap potential expressions that can return multiple values to avoid creating a bigger table than needed. So ... and functions calls should be wrapped in parentheses!" I'm not sure what ... and function calls are actually meaning.
Can you please provide more example codes so can you help me I understand? Sorry about that.
but sorry. one thing that I'm not quite understanding is "We should also wrap potential expressions that can return multiple values to avoid creating a bigger table than needed. So
...and functions calls should be wrapped in parentheses!" I'm not sure what...and function calls are actually meaning.Can you please provide more example codes so can you help me I understand? Sorry about that.
Of course 😄 Take this example:
local function oof(...: string)
return if condition(...) then ... else transform(...)
end
-- converts to
local function oof(...: string)
return (condition() and { (...) } or { transform(...) })[1]
end
- Imagine that
oofis called with multiple arguments,...would expand in the table and put more values than needed. - Imagine that
transform(...)returns multiple values, it would also expand in the table and put more values than needed again
The wrapping in parentheses will make the extra values go away.
but sorry. one thing that I'm not quite understanding is "We should also wrap potential expressions that can return multiple values to avoid creating a bigger table than needed. So
...and functions calls should be wrapped in parentheses!" I'm not sure what...and function calls are actually meaning. Can you please provide more example codes so can you help me I understand? Sorry about that.Of course 😄 Take this example:
local function oof(...: string) return if condition(...) then ... else transform(...) end -- converts to local function oof(...: string) return (condition() and { (...) } or { transform(...) })[1] end
- Imagine that
oofis called with multiple arguments,...would expand in the table and put more values than needed.- Imagine that
transform(...)returns multiple values, it would also expand in the table and put more values than needed againThe wrapping in parentheses will make the extra values go away.
Thank you so much! It seems possible to implement easily except for elseif hell... hm
This PR may close #207
In my opinion (as stated in #212), this is how the transformation should happen:
local maxValue = if a > b then a else b
goes to
local maxValue
if a > b then
maxValue = a
else
maxValue = b
end
Why
Using an if saves a few instructions and is truer to what it actually Luau compiles the statement to.
function anon_0(??)
1 GETIMPORT R2 1 ; a
2 GETIMPORT R3 3 ; b
3 JUMPIFNOTLT R3 R2 L0
4 NEWTABLE R1 0 1
5 GETIMPORT R2 1 ; a
6 SETLIST R1 R2 1 ; 1
7 JUMPIF R1 L1
8 L0 NEWTABLE R1 0 1
9 GETIMPORT R2 3 ; b
10 SETLIST R1 R2 1 ; 1
11 L1 GETTABLEN R0 R1 1
12 GETIMPORT R1 5 ; print
13 MOVE R2 R0
14 CALL R1 1 0
15 RETURN R0 0
end
function anon_1(??)
1 LOADNIL R0
2 GETIMPORT R1 1 ; a
3 GETIMPORT R2 3 ; b
4 JUMPIFNOTLT R2 R1 L0
5 GETIMPORT R0 1 ; a
6 JUMP L1
7 L0 GETIMPORT R0 3 ; b
8 L1 GETIMPORT R1 5 ; print
9 MOVE R2 R0
10 CALL R1 1 0
11 RETURN R0 0
end
function anon_2(??)
1 GETIMPORT R1 1 ; a
2 GETIMPORT R2 3 ; b
3 JUMPIFNOTLT R2 R1 L0
4 GETIMPORT R0 1 ; a
5 JUMP L1
6 L0 GETIMPORT R0 3 ; b
7 L1 GETIMPORT R1 5 ; print
8 MOVE R2 R0
9 CALL R1 1 0
10 RETURN R0 0
end
Samples (in order)
local maxValue = ((a > b) and {(a)} or {(b)})[1]
print(maxValue)
local maxValue
if a > b then
maxValue = a
else
maxValue = b
end
print(maxValue)
local maxValue = if a > b then a else b
print(maxValue)
In my opinion (as stated in #212), this is how the transformation should happen:
local maxValue = if a > b then a else bgoes to
local maxValue if a > b then maxValue = a else maxValue = b endWhy
Using an
ifsaves a few instructions and is truer to what it actually Luau compiles the statement to.function anon_0(??) 1 GETIMPORT R2 1 ; a 2 GETIMPORT R3 3 ; b 3 JUMPIFNOTLT R3 R2 L0 4 NEWTABLE R1 0 1 5 GETIMPORT R2 1 ; a 6 SETLIST R1 R2 1 ; 1 7 JUMPIF R1 L1 8 L0 NEWTABLE R1 0 1 9 GETIMPORT R2 3 ; b 10 SETLIST R1 R2 1 ; 1 11 L1 GETTABLEN R0 R1 1 12 GETIMPORT R1 5 ; print 13 MOVE R2 R0 14 CALL R1 1 0 15 RETURN R0 0 end function anon_1(??) 1 LOADNIL R0 2 GETIMPORT R1 1 ; a 3 GETIMPORT R2 3 ; b 4 JUMPIFNOTLT R2 R1 L0 5 GETIMPORT R0 1 ; a 6 JUMP L1 7 L0 GETIMPORT R0 3 ; b 8 L1 GETIMPORT R1 5 ; print 9 MOVE R2 R0 10 CALL R1 1 0 11 RETURN R0 0 end function anon_2(??) 1 GETIMPORT R1 1 ; a 2 GETIMPORT R2 3 ; b 3 JUMPIFNOTLT R2 R1 L0 4 GETIMPORT R0 1 ; a 5 JUMP L1 6 L0 GETIMPORT R0 3 ; b 7 L1 GETIMPORT R1 5 ; print 8 MOVE R2 R0 9 CALL R1 1 0 10 RETURN R0 0 endSamples (in order)
local maxValue = ((a > b) and {(a)} or {(b)})[1] print(maxValue)local maxValue if a > b then maxValue = a else maxValue = b end print(maxValue)local maxValue = if a > b then a else b print(maxValue)
It is not that easy to convert if expression into if statement since the case like this code above would be a nightmare of side effects
local c = ...
local a, c = sideEffect() + if condition then compute(c) else default(), resetC()
BTW, it was my first idea and the second idea was using closures then the final idea is using tables and and/or operators
I'm on mobile but I believe that
local c = ...
local a, c = sideEffect() + if condition then compute(c) else default(), resetC()
compiles back to
local c = ...
local a = sideEffect() + if condition then compute(c) else default()
local c = resetC()
where the results of sideEffect and the if expr are stored in separate registers before added up so it's basically equal to
local c = ...
local a = sideEffect()
a += if condition then compute(c) else default()
local c = resetC()
Looking at the bytecode, it's executed as expected in order from left to right. sideEffect is callled then stored and finally added to the result of the if expr. Then, resetC is called.
Similar to how darklua handles a[foo()] += 1, a temporary variable could be made that computes the final value.
However, I can acknowledge how it may be difficult to know when or how to split the expression up. For example, setting a equal to the result then setting a again but with the if expr result.
I'm not too experienced with these things but Rust should make it easier since you can match Expr and make sure all cases are covered.