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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Cross-compile Akka with Scala 3 #29929
Conversation
Thank you for your pull request! After a quick sanity check one of the team will reply with 'OK 韦O 韦ES韦' to kick off our automated validation on Jenkins. This compiles the project, runs the tests, and checks for things like binary compatibility and source code formatting. When two team members have also manually reviewed and (perhaps after asking for some amendments) accepted your contribution, it should be good to be merged. For more details about our contributing process, check out CONTRIBUTING.md - and feel free to ask! |
@@ -16,6 +16,9 @@ import akka.actor._ | |||
*/ | |||
class AkkaExceptionSpec extends AnyWordSpec with Matchers { | |||
|
|||
// TODO DOTTY, yes I know it's wrong | |||
implicit val pos: org.scalactic.source.Position = new org.scalactic.source.Position(fileName = "", filePathname = "", lineNumber = 1) |
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 is a workaround, ScalaTest is usable with Scala 3, but I found a few rough edges.
The implicit position is provided from a macro in Scala 2.X and is not available in Scala 3.
I see that there is some ongoing work to cross-compile ScalaTest (e.g. scalatest/scalatest@e8201d4 ) and I expect that this issue is solved there, maybe @bvenners or @cheeseng can chip in?
Reference issue:
scalatest/scalatest#1950
implicit val pos: org.scalactic.source.Position = new org.scalactic.source.Position(fileName = "", filePathname = "", lineNumber = 1) | ||
|
||
// TODO DOTTY | ||
protected override def runTest(testName: String, args: org.scalatest.Args): org.scalatest.Status = akka.testkit.ScalatestRunTest.scalatestRunTest(testName, args) |
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.
@@ -466,8 +466,8 @@ class ActorRefSpec extends AkkaSpec(""" | |||
def receive = { case name: String => sender() ! context.child(name).isDefined } | |||
}), "parent") | |||
|
|||
assert(Await.result((parent ? "child"), timeout.duration) === true) |
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.
assert
is a macro and is not usable "as is", for the moment I switched to require
, but I was expecting this to work out of the box, maybe @nicolasstucki or @smarter can shed a light?
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.
What was the issue with assert
?
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.
Since assert
is a macro Scala 3 rejects it.
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.
@nicolasstucki there's no actual issue, this project is just using a scala 2 version of scalatest instead of the scala 3 version: #29929 (comment)
@@ -51,7 +59,7 @@ class DynamicAccessSpec extends AnyWordSpec with Matchers with BeforeAndAfterAll | |||
instantiateWithDefaultOrStringCtor("akka.actor.TestClassWithDefaultConstructor").get.name shouldBe "default" | |||
instantiateWithDefaultOrStringCtor("akka.actor.foo.NonExistingClass") match { | |||
case Failure(t) => | |||
t shouldBe a[ClassNotFoundException] |
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.
} | ||
|
||
} | ||
// "A TypedMultiMap" must { |
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 guess that TypedMultiMap
types signatures are pretty bound to Scala 2, anyone I can ping on the subject?
maybe @raboof or @johanandren can point me to the right person?
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's our own impl so not sure anyone is more or less suitable to say anything about it without looking at in which way it does not work on Scala 3. Perhaps it would be possible to implement a source compatible version specifically for Scala 3?
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 will take a look and give it a spin.
ActorCell.class.getDeclaredField( | ||
"akka$actor$dungeon$Dispatch$$_mailboxDoNotCallMeDirectly")); | ||
// TODO DOTTY | ||
ActorCell.class.getDeclaredField("_mailboxDoNotCallMeDirectly")); |
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.
Here I need some guidance on how to proceed, this AFAIU prevents today to use Akka Actor in a Scala 3 project.
But, at the same time, rely on the internals of the compiler, should we implement fallbacks or tackle the problem differently? cc. @raboof @johanandren @patriknw
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.
@eloots did you not hit this in https://github.com/eloots/akka-typed-on-scala-3 ?
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 assume that @eloots was using exclusively Akka Typed, probably not hitting the untyped ActorCell. But this needs to be proved ...
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 offsets here is used by the underlying impl also for typed actors (those are on top of "classic" actors), so that should be involved regardless. But Erics sample just used the pre-compiled 2.13 Akka artifacts rather than compiling them with Scala 3 himself, so maybe that keeps the encoding.
I guess this would have to be separate logic for Scala 2 and 3 somehow?
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'll double-check that this is needed after the rest of the more "trivial" changes get merged, in case I'll go for the switch case. (Do we already have available the scalaVersion
in the runtime, such as BuildInfo
or something like Version
?)
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.
Neither Scala 2 nor Scala 3 give any guarantee about the naming scheme of private fields, so while this might work on a particular version of a compiler, it might not work on the next.
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.
While I understand there is no such guarantee/promise, it has been stable in Scala 2 at least since 2012 (and Scala 2.10.0-M7). This will likely be tricky to solve if the internal encoding in Scala 3 is expected to change often.
Perhaps moving on to VarHandle from Unsafe could solve it, but we can't do that as long as we also support Java 8 (I have a feeling that there was performance concerns around that as well).
@SerialVersionUID(1L) | ||
// TODO DOTTY | ||
// @SerialVersionUID does nothing on a trait | ||
// @SerialVersionUID(1L) |
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'm not sure why we add an annotation and the silencer to prevent the compiler to say that it's not going to work, any help @raboof @johanandren ?
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.
Both the concrete subclasses have their own @serialversionuid, I think it is completely fine to remove here as it doesn't do anything anyway?
@@ -128,7 +128,10 @@ class LightArrayRevolverScheduler(config: Config, log: LoggingAdapter, threadFac | |||
} | |||
} | |||
|
|||
@tailrec final def cancel(): Boolean = { | |||
// TODO DOTTY | |||
// Cannot rewrite recursive call: it targets a supertype |
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'm puzzled by this error message, is it the expected behavior @nicolasstucki, @smarter ?
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 error message refers toc.cancel()
which is an overridden method of the current cancel()
method, dotty correctly tells you that it cannot rewrite it, scala 2 doesn't say anything here, it's not a bug, just a difference of what @tailrec
checks.
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.
done in #29939
@silent @volatile private var _cellDoNotCallMeDirectly: Cell = _ | ||
@silent @volatile private var _lookupDoNotCallMeDirectly: Cell = _ | ||
// TODO DOTTY | ||
@silent @volatile protected var _cellDoNotCallMeDirectly: Cell = _ |
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.
Here is a real "issue", e.g. something that is ok for a POC but is not going to be merged on master.
My understanding is that private unused variables are erased by the Scala 3 compiler, is there a way to preserve them(with an annotation or something) since in this case they are used entirely through Unsafe
?
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.
Make a def: ohze@26394d4
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.
done in #29930
final case class Envelope private (message: Any, sender: ActorRef) | ||
final case class Envelope private (message: Any, sender: ActorRef) { | ||
// TODO DOTTY -> bug? copy method becomes private with private constructor | ||
def copy(message: Any, sender: ActorRef) = Envelope(message, sender) |
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.
In Dotty the copy
method of case class
es have the same visibility as the constructor AFAIU.
Is it a deliberate change from Scala 2 to Scala 3 or is it accidental? @nicolasstucki @smarter
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.
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.
Thanks @SethTisue ! Means we have to declare them explicitly.
@@ -15,7 +15,8 @@ import akka.util.unused | |||
* | |||
* Watches all actors which subscribe on the given event stream, and unsubscribes them from it when they are Terminated. | |||
*/ | |||
protected[akka] class ActorClassificationUnsubscriber(bus: ManagedActorClassification, debug: Boolean) | |||
// TODO DOTTY -> Something wrong is happening in the compiler | |||
protected[akka] class ActorClassificationUnsubscriber(bus: String, unsubscribe: ActorRef => Unit, debug: Boolean) |
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'm not sure why, but the compiler go completely nuts on this detail, the pattern is something like:
file1.scala
class A {
val b = new B(this)
def foo() = {}
}
file2.scala
class B(a: A) {
def bar() = {
a.foo()
}
}
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 is not enough details for us to say anything (what does "completely nuts" mean?).
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 compiler rejects the build saying that one of the class file is missing.
It builds although using incremental compilation by removing dependencies on the other compilation unit and re-adding them back.
I can try to to provide a branch as a repro, sorry for the lack of details.
block: function.Function2[GraphDSL.Builder[M], S1, S]): Graph[S, M] = | ||
scaladsl.GraphDSL.create(g1) { b => s => block.apply(b.asJava, s) } | ||
scaladsl.GraphDSL.create(g1) { b => (s: Shape @uncheckedVariance) => block.apply(b.asJava, s.asInstanceOf[S1]) } |
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 solves a bunch of errors but is probably wrong
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.
In general, when Scala 3 requires @uncheckedVariance
where Scala 2 doesn't, it's entirely plausible that the Scala 2 compiler was wrongly accepting unsound code. (But that's in general; I haven't dug into this particular case.)
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 original sin here is the definition of Shape with @uncheckedVariance
:
type Shape = S @uncheckedVariance |
This breaks soundness as demonstrated by https://scastie.scala-lang.org/smarter/Cno5sLZyTSybDC80uDt2hQ, meaning that we can't really guarantee anything about type-safety in akka.
@@ -23,7 +23,22 @@ import akka.pattern.ask | |||
* @since 1.1 | |||
*/ | |||
@silent // 'early initializers' are deprecated on 2.13 and will be replaced with trait parameters on 2.14. https://github.com/akka/akka/issues/26753 |
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.
Should we move this implementation to the scala-3
folder and use proper trait parameters in Scala 3?
@raboof @johanandren
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.
If that works with minimal changes it sound fine by me
@@ -64,10 +64,14 @@ object AkkaDisciplinePlugin extends AutoPlugin { | |||
"akka-testkit") | |||
|
|||
lazy val silencerSettings = { | |||
val silencerVersion = "1.7.1" | |||
val silencerVersion = "1.5-SNAPSHOT" | |||
// TODO: FIXME DOTTY (using a locally published version of silencer...) |
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 simply published locally an "empty version" of the silencer, any plan to port it to Scala 3 already on-going @ghik ?
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.
Does Scala 3 support @nowarn
? I would expect that it does. All @silent
annotations should be migrated to @nowarn
- silencer would then be necessary only in Scala 2.12 (it understands @nowarn
to some extent).
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.
Thanks @ghik for the answer!
The question then goes to the Akka Team ( @johanandren @raboof ): can we migrate all the annotations to @nowarn
in the entire code-base, and tweak compiler flags to be more lenient in Scala 2.12?
Or, we can define a local annotation @nowarn
in the scala-212
folder that simply rebind silence
(have to test this option).
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.
@nowarn
is already available for Scala 2.12 in scala-collection-compat. It is partially interpreted by silencer.
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.
@nowarn
will be in Scala 2.12.13 (scala/scala#9248), so I think it'd make sense to wait for that.
Do we need to make our flags more lenient in order to move to @nowarn
?
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 need to make our flags more lenient in order to move to @nowarn?
Don't know, will test!
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.
@nowarn
is not implemented in Scala 3 yet: scala/scala3#8908
|
||
val lmdb = "org.lmdbjava" % "lmdbjava" % "0.7.0" // ApacheV2, OpenLDAP Public License | ||
|
||
val junit = "junit" % "junit" % junitVersion // Common Public License 1.0 | ||
|
||
// For Java 8 Conversions | ||
val java8Compat = Def.setting { "org.scala-lang.modules" %% "scala-java8-compat" % java8CompatVersion.value } // Scala License | ||
// TODO DOTTY | ||
val java8Compat = Def.setting { DottyCompatModuleID("org.scala-lang.modules" %% "scala-java8-compat" % java8CompatVersion.value).withDottyCompat(getVersion()) } // Scala License |
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 is a library that probably we want to, eventually, cross-compile as well for Scala 3, cc. @SethTisue
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.
yeah, scala/scala-java8-compat#200 ... we're hoping someone will volunteer to tackle it. if you have any problems using it with withDottyCompat
, let me know, I would consider that high priority to investigate
val scalatest = "org.scalatest" %% "scalatest" % scalaTestVersion % "test" // ApacheV2 | ||
val scalacheck = "org.scalacheck" %% "scalacheck" % scalaCheckVersion % "test" // New BSD | ||
// TODO DOTTY | ||
val scalatest = DottyCompatModuleID("org.scalatest" %% "scalatest" % scalaTestVersion % "test").withDottyCompat(getVersion()) // ApacheV2 |
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.
All your problems related to scalatest stem from the fact that you're using the Scala 2 version of scalatest, so none of the macros are usable. You should drop the withDottyCompat
for all dependencies which are published for Scala 3, this includes scalatest: https://repo1.maven.org/maven2/org/scalatest/scalatest_3.0.0-M3/
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.
Amazing didn't know about it! Thanks a lot @smarter !
Unfortunately, Scala 3 is not listed by Maven central:
https://mvnrepository.com/artifact/org.scalatest/scalatest
How do we know if a library is available or we should use the compatibility mode?
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.
scaladex can help: https://index.scala-lang.org/scalatest/scalatest/scalatest/3.2.3 (see the "version matrix" button on the right), or just browse https://repo1.maven.org/maven2 manually.
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.
Is Scala 3.0.0-M3
still missing in Scaladex?
And, found that, probably, this is a good starting point:
https://index.scala-lang.org/search?q=depends-on:lampepfl/scala3-library&page=1
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.
done in #29930
FYI, there's a wip dotty branch for akka in https://github.com/ohze/akka/commits/dotty/wip by @ohze / @giabao, you might want to collaborate. |
Superseded by #29956, I assume |
This PR aims to kick start the work and conversations on cross-compiling Akka with, upcoming, Scala 3.
Current status:
Modules
akka-actor
,akka-testkit
,akka-actor-tests
are compiling and all of the tests are passing locally (butTypedMultiMapSpec
which is commented) starting sbt withsbt -Dakka.build.scalaVersion=3.0
.Started cross-compiling
akka-stream
which is down to ~50 compile-time errors, but I have a problem with variance (i.e.@uncheckedVariance
) that needs further attention.There are a number of small changes that are simple refactorings and I will follow up with separate PRs to current
master
to reduce the diff (e.g. extra import to enable the bang operator).I'll comment on the diff a bunch of additional points that came out and try to ping relevant ppl for help 馃檹
Note: This is a personal initiative started during the Christmas break 馃檪