-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Treat Scala.js pseudo-unions as real unions #11671
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
Thank you for this!
Can we fix it by automatically introducing the following import under import scala.scalajs.js.|.undefOr2ops The other two implicit conversions (
AFAICT, ignoring those imports would be fine, yes. |
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 implementation looks good to me so far.
Sure, can you check if this import is enough to fix the sjsJUnitTests? |
It does fix all the errors related to methods of However, there are still a few errors left. One seems to be worse type inference for
I guess we could live with that. The others suggest that the unpickling is not always doing the right thing, especially if a parameter is a
All of them are clearly wrong, since they all amount to things like "Found: X but required: X | Something". |
I think those are due to |
Ah, some of them, indeed. By unimporting that, and rewriting two
The first one is the same type inference issue |
Could you try to minimize those and push them as either standalone tests or as part of the erasure-scalajs scripted test I added?
This has likely something to do with the special way we need to typecheck the rhs of default getters (https://github.com/lampepfl/dotty/blob/95fa9cb9f3a6989ae41bbe047fc977cc33716b87/compiler/src/dotty/tools/dotc/typer/Namer.scala#L1389-L1394) |
Unfortunately just importing undefOr2ops breaks more stuff than it fixes, the problem is that it's applicable for any subtype of |
My best idea would be to inject undefOr2ops in OfTypeImplicits#ref when tp is an OrType: https://github.com/lampepfl/dotty/blob/95fa9cb9f3a6989ae41bbe047fc977cc33716b87/compiler/src/dotty/tools/dotc/typer/Implicits.scala#L272-L275 |
This seems to work OK, but it's a pretty weird special case, should these operations be deprecated eventually? |
To limit the impact of this thing, we could restrict the injection of undefOr2ops to only happen when the alias |
Ideally, this extension would be in the companion object of Could we inject its definition in the companion object of
Eventually, if and when we have flow typing for |
Injecting just that definition and not the rest of |
Would it help if we had another |
Yes, in that case we could easily add it when we collect the companions if tp is Unit: https://github.com/lampepfl/dotty/blob/c414e8ce0845cf9c6dd8384688f3d534f23cdf69/compiler/src/dotty/tools/dotc/typer/Implicits.scala#L664 |
Then I guess we can put the following in a new file package scala.scalajs.js.internal
import scala.scalajs.js
object UnitOps:
implicit def unitOrOps[A](x: A | Unit): js.UndefOrOps[A] =
new js.UndefOrOps(x) and add the directory to the build by replacing unmanagedSourceDirectories in Compile := {
val shared = (unmanagedSourceDirectories in (`scala3-library-bootstrapped`, Compile)).value
val jsSpecified = baseDirectory.value / "src"
shared :+ jsSpecific
}, |
I think the right thing to do here is get it working without |
I'm working on a fix for the default getter issue, locally the remaining issues I see cannot be fixed in the dotty repo itself: [error] -- [E081] Type Error: /home/smarter/opt/dotty/tests/sjs-junit/../out/bootstrap/sjsJUnitTests/scala-js-src-1.4.0/test-suite/js/src/test/require-2.12/org/scalajs/testsuite/jsinterop/JSOptionalTest212.scala:204:34 --------------------------
[error] 204 | override val f = js.defined(x => x + 1)
[error] | ^
[error] | Missing parameter type
[error] |
[error] | I could not infer the type of the parameter x.
[error] -- Error: /home/smarter/opt/dotty/tests/sjs-junit/../out/bootstrap/sjsJUnitTests/scala-js-src-1.4.0/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/PromiseMock.scala:169:13 ---------------------------------------------------
[error] 169 | if ((resolution: AnyRef) eq (this: AnyRef)) {
[error] | ^^^^^^^^^^
[error] | the result of an implicit conversion must be more specific than AnyRef
@sjrd can you update the test suite upstream to fix those? |
I can definitely change the second one to use For the first one, the whole point of that test is to make sure that the parameter type can be inferred in that very situation, so I will instead isolate it in a separate file that we can blacklist in the dotty build. In the meantime, you can already blacklist Blacklisting |
Sounds good, though depending on when the next scala.js release is out we might have to disable these tests to get this PR into RC2. |
I think it's fine to disable them for now. Also doing that might uncover other issues down the line, which we should also address in a release of Scala.js, so it's better if we can discover and food everything as soon as possible. |
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.
Beautiful! Thank you so much for doing this.
I only have a few minor suggestions.
This will be used in the next commit to deal with Scala.js unions. Also change the erasure of value classes to preserve flags like `isSymbol` when erasing its underlying type.
When unpickling a Scala 2 symbol, we now treat `scala.scalajs.js.|[A, B]` as if it were a real `A | B`, this requires a special-case in erasure to emit the correct signatures in SJSIR. Remaining issues after this commit fixed in later commits of this PR: - The companion object of `js.|` defines implicit conversions like `undefOr2ops`, those are no longer automatically in scope for values with a union type (which breaks many JUnitTests). - When compiling Scala.js code from source, `A | B` is interpreted as `js.|[A, B]` if `js.|` is in scope (e.g. via an import). Additionally, a few tests had to be disabled until we can depend on their fixed upstream version (scala-js/scala-js#4451).
This fixes various compilation errors with sjsJUnitTests after the previous commit.
The companion of `js.|` contains the following widely used implicit conversion to a value class: /** Provides an [[Option]]-like API to [[js.UndefOr]]. */ implicit def undefOr2ops[A](value: js.UndefOr[A]): js.UndefOrOps[A] = new js.UndefOrOps(value) (where `UndefOr[A]` dealiases to `A | Unit`). Since we re-interpret Scala.js unions as real unions, this companion is not in the implicit scope of `A | Unit`, we work around this by injecting a new `UnitOps` with the implicit conversion we want in the implicit scope of `Unit` (we could have directly injected the object `js.|`, but that contains other implicits we do not need). This finally lets us compile and run the sjsJUnitTests after the previous two commits.
To improve our testing of unions until we get a release of Scala.js with the fixed PromiseMock. This also lets us re-enable AsyncTest which depends on PromiseMock.
Now that we unpickle Scala.js unions as actual unions, these implicits are suddenly applicable in many more situations and can end up polluting error messages with implicit suggestions since they should never be used explicitly, so blacklist them from the suggestions.
Disallow lambdas in statement position or if the expected type is Unit. Fixes scala#11671
Disallow lambdas in statement position or if the expected type is Unit. Fixes scala#11671
When unpickling a Scala 2 symbol, we now treat
scala.scalajs.js.|[A, B]
as if it were a real
A | B
, this requires a special-case in erasureto emit the correct signatures in SJSIR.
Unresolved issues:The companion object of
js.|
defines implicit conversions likeundefOr2ops
, those are no longer automatically in scope for valueswith a union type (which breaks many JUnitTests). Should we add a
special-case in typer to handle this or can this be fixed on the
scalajs-library side?
When compiling Scala.js code from source,A | B
is interpretedas
js.|[A, B]
ifjs.|
is in scope (e.g. via an import),should we just ignore that import in typer?