-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Scala.js: Support for reflective calls. #9427
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! ❤️
Commit Messages
We want to keep history, but for that to actually be useful we have
some rules on how to format our commit messages (relevant xkcd).
Please stick to these guidelines for commit messages:
- Separate subject from body with a blank line
- When fixing an issue, start your commit message with
Fix #<ISSUE-NBR>:
- Limit the subject line to 72 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line ("Add" instead of "Added")
- Wrap the body at 80 characters
- Use the body to explain what and why vs. how
adapted from https://chris.beams.io/posts/git-commit
Have an awesome day! ☀️
30cb648
to
c99b7e0
Compare
val receiver = receiver0 match { | ||
case Apply(fun1, receiver :: _) | ||
if fun1.symbol == jsdefn.ReflectSelectable_reflectiveSelectable || | ||
fun1.symbol == jsdefn.Selectable_reflectiveSelectableFromLangReflectiveCalls => |
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 wonder if you could make that conversion method inline to avoid having to deal with it here.
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 tried that initially, but if I do that, then suddenly Type Mismatch errors anywhere suggests to import that implicit conversion because it could "make progress towards a solution" ... even though the result type is monomorphically scala.reflect.Selectable
, even for type mismatches like "Expected String but found Boolean".
I think the suggestion mechanism assumes that any inline implicit def
could return a more precise type than what is declared (because you know, blackbox macros are just whitebox macros with a special body, so nobody can tell from the outside, which is a problem that seems to pop up every now and then in various scenarios).
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 think the suggestion mechanism assumes that any inline implicit def could return a more precise type than what is declared (because you know, blackbox macros are just whitebox macros with a special body, so nobody can tell from the outside, which is a problem that seems to pop up every now and then in various scenarios).
@nicolasstucki What's the current status of whether transparent
should be a flag or not? Is this a known issue with the current encoding?
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.
Anyway, for this specific case, I figured it would be better not to have it inline
, otherwise it gets sketchy to rely on the fact that the inliner will preserve the shape of the tree, and in particular that the fun1
directly inside the Apply
will indeed be the call to reflectiveSelectable
(and not a Block
that ends in a call to reflectiveSelectable
, notably).
"Passing the varargs of Selectable.applyDynamic with `: _*` " + | ||
"is not supported in Scala.js.", |
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.
Do we have neg tests for the various error conditions in this method?
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.
No. I'm not sure yet how to write negative tests for Scala.js-specific error messages.
* - the actual arguments as a compile-time `List[Tree]` | ||
* | ||
* Virtually all of the code in `genReflectiveCall` deals with recovering | ||
* those elements. Constructing the IR Tree is the easy part after that. |
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.
Using a thing like scala.reflect.Selectable
seems like a neat trick to easily implement reflective calls on backends that support reflection.
However, here it seems that this trick is now doing the entire compiler a disservice: We have ~100 LOC simply reconstructing things and then a single LOC actually issuing the call. (and additionally failures that IIUC simply cannot happen with reflective calls).
Have you considered introducing a form of transient tree and doing the translation to a call to Selectable
later? (I realize that the call relies on other transformations that happen earlier, so it might be worse overall).
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 agree that the situation is very inconvenient. But the desugaring that is now done in Typer is actually specced as is now. It's not "a trick". It is the normal way of compiling calls to members of structural types. reflectiveSelectable
is just one possible implicit that can provide an implementation for those, but there can be other, user-defined implementation (that don't really on Java reflection, but on an internal Map, for example).
The reference is here: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html
We need to support reflectiveSelectable
because it corresponds to Scala 2 reflective calls, and it's the only way that our back-end would emit calls to reflective proxies (which we really need in some cases; we use them in the JDK implem in Scala.js itself for example).
Users can implement other Selectable
s not relying on Java run-time reflection, and those would also work.
So we can't introduce a form of transient tree to be desugared later. The desugaring is by spec and requires interactions with other tasks of typechecking, including implicit resolution. It's impossible to delay that.
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.
Hmmm... I see. So is the expectation that any custom Selectable
that needs compile time reflection needs to do this ceremony? The page you linked doesn't go into details about that.
Further, if that is the case, shouldn't we introduce another Selectable
for Scala.js? The main issue I see here is that with the current specification, it would be allowed to use reflectiveSelectable
on its own, without structural types. However, such use would immediately lead to non-portable code. Maybe we should make this more obvious?
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 realize this is probably a larger discussion, so you might want to go ahead with this PR. I still think it is worth thinking about this a bit more.
|
||
val methodName = MethodName.reflectiveProxy(methodNameStr, formalParamTypeRefs) | ||
|
||
js.Apply(js.ApplyFlags.empty, receiver, js.MethodIdent(methodName), actualArgs)(jstpe.AnyType) |
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.
Judging from the code of reflect.Selectable
, selectDynamic
is able to select actual bytecode fields (as opposed to accessors). Then again, it falls back to applyDynamic
.
I do not know the details of how dotty translates fields, but it seems this is problematic for exact compatibility.
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.
On the JVM, we need to look up fields because of Java interop. A Java class such as
public class A {
public int foo = 5;
}
qualifies for the Scala structural type
{ val foo: Int }
so for Java we need to try fields.
In the SJSIR, however, since JDK classes are implemented in Scala anyway, I don't think there can be an issue.
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.
Ah, OK. Fair enough.
c99b7e0
to
73b34b1
Compare
Updated to address the reviews. |
The translation of `classOf[Prim]` (e.g., `classOf[Int]`) to `BoxedPrimClass.TYPE` (e.g., `j.l.Integer.TYPE`) is specific to the JVM back-end, because the JVM bytecode does not have any instruction to load them. Other back-ends have appropriate opcodes and prefer to receive them as `Constant(Literal(primType))`, so that they don't have to reverse-engineer the `Select`s. Since that transformation is not harder to do in the back-end, we move it directly to the JVM codegen. The code is actually shorter there. This also helps to keep the trees a bit smaller across the phases after erasure.
This is significantly different from the code in scalac. In scalac, reflective calls reach the back-end as dedicated `ApplyDynamic` nodes. In dotc however, they are expanded by the type-checker into user-level calls to method of `scala.reflect.Selectable`. We must therefore reverse-engineer the patterns of code produced by the compiler to construct SJSIR `Apply` nodes with reflective proxy method names. The big comment on `JSCodeGen.genReflectiveCall` explains the translation in detail.
73b34b1
to
d5e8831
Compare
Ping @smarter. Is there still something you'd like me to do here? How much do you care about the neg tests at this point? (at some point I'll need to figure them out, because we've got loads of Scala.js-specific error messages in our front-end phase, which does not exist at all in dotc) |
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.
The neg tests can wait.
Based on #9420 (the first commit).This is significantly different from the code in scalac. In scalac, reflective calls reach the back-end as dedicated
ApplyDynamic
nodes. In dotc however, they are expanded by the type-checker into user-level calls to method ofscala.reflect.Selectable
.We must therefore reverse-engineer the patterns of code produced by the compiler to construct SJSIR
Apply
nodes with reflective proxy method names.The big comment on
JSCodeGen.genReflectiveCall
explains the translation in detail.