-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Partial fix for lampepfl#7113: Fix Scala.js codegen for Explicit Returns #7439
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, and thank you for opening this PR! 🎉
All contributors have signed the CLA, thank you! ❤️
Have an awesome day! ☀️
The fix looks correct, but I believe it also applies to user-written (local) |
@@ -92,6 +92,16 @@ object JSEncoding { | |||
returnLabelName = Some(freshName("_return")) | |||
js.Ident(returnLabelName.get) | |||
} | |||
|
|||
def withNewReturnableScope(tpe: jstpe.Type)(body: => js.Tree)(implicit pos: ir.Position): js.Tree = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems body
doesn't need to be by-name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is needed. returnLabelName
will be mutated from None
to Some(...)
somewhere inside the execution of body
(or not if there is no Return()
node inside the body).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be missing something. The value of returnLabelName
is not saved (or used) before the evaluation of body
. So what difference does it make to evaluate it before the function is called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right. I didn't think about it like that. Yeah I guess it can be made a by-value parameter, then, but I'd advocate for a different name in that case, for example wrapInReturnLabeledIfRequired
or something like that. The name withXyz
suggests that something by-name is happening, at least to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch - the Scala 2 version of this used ScopedVars in a way that required by-name, but as a mutable handle to the same localNames
, the Dotty version has no such need. Made this by-value and updated the method's name accordingly.
2ad91c1
to
3f43614
Compare
This does indeed affect all explicit local return statements, whether in or out of a loop. A simple example with an early return
yielded the following as the resulting IR:
While this isn't strictly necessary for this example (an equivalent transformation could simply assign the value to a temporary variable and return its value later), it does avoid returning the wrong value, and becomes vitally import for early returns inside of otherwise inescapable loops. I also fixed up the commit message, and changed the unnecessary by-name parameter to by-value (along with changing the method name, and giving it an accompanying comment to make what is happening there more clear). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One tiny detail, see below.
|
||
/** If the evaluation of `body` adds a `returnLabelName`, wrap the resulting js.Tree to use that label */ | ||
def makeLabeledIfRequiresEnclosingReturn(tpe: jstpe.Type)(body: js.Tree)(implicit pos: ir.Position): js.Tree = { | ||
val inner = body |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This val
is not needed anymore, now that body
is by-value. You can directly use body
inside the cases of the match.
Can someone from the dotty team (@anatoliykmetyuk ?) say whether it makes sense for the commit message to include |
I would put |
In order to use an explicit return statement in Scala.js IR, a label must be placed wrapping the whole function body to serve as a jump/break point for all control structures inside the function. While a fresh return label had been generated for such methods (in the LocalNameGenerator), it had not been placed at the top level of its method, and was otherwise unused. This affected the tree created by the TailRec MiniPhase, as well as user-written early return statements. After generating a method's body, if a enclosing return label had been generated, use it to wrap the method body (just as Scala 2's JS codegen does) for such breaks. Enable the tests of ReadersTest.scala of the Scala.js test suite, of which InputStreamReaderTest used a tail-recursive method that depended on this fix.
3f43614
to
00d4216
Compare
I think a good idea would be to make a #7113 a milestone composed of multiple issues, or else define what needs to be done precisely and concisely and place this info in the first message of that issue. The "Partial fix" wording implies a bug-grade issue. From the current info in #7113, it is hard to judge at a glance the scope of work intended. If someone looks at these commits a year from now and attempts to trace the reasoning behind them to the original issue, they will lose time reading all the info at best, be lost at worst. Another option is to mention the issue as "See #7113 for the discussion" in the subsequent commits. |
In order to return from a tail recursive tree in Scala.js IR, a label
must be placed outside the loop to serve as a jump/break point. While
a fresh return label had been generated for such methods (in the
LocalNameGenerator), it had not been placed at the top level of its
method, and was otherwise unused.
After generating a method's body, if a enclosing return label had been
generated, use it to wrap the method body (just as Scala 2's JS codegen
does) for such breaks.
Enable the tests of ReadersTest.scala of the Scala.js test suite, of
which InputStreamReaderTest used a tail-recursive method that depended
on this fix.