-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Enable generation of TASTy files readable for older compilers #14156
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
Changes from 1 commit
a398130
14719cf
99e0cc2
1c17ea8
1455c4b
22e8f8f
ab3efcf
44ffb68
f75c5ee
f1bae32
465ef4c
42160dc
46b59c9
72cb484
52f0177
e39b618
e1f00c8
f2aede6
3f88de4
a86db05
0c8ac84
85cf9be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,18 @@ layout: doc-page | |
title: "Binary Compatibility" | ||
--- | ||
|
||
Thanks to TASTy files, which are produced during compilation of Scala 3 sources, unlike Scala 2, Scala 3 is backward binary compatible between different minor versions (i.e. binary artifacts produced with Scala 3.x can be consumed by Scala 3.y programs as long as x <= y). | ||
There are however already some ongoing attempts to make Scala 3 forward binary compatible which means that, with some restrictions, it might be possible to compile Scala code with a newer version of the compiler and then use the produced binaries as dependencies for a project using an older compiler. | ||
In Scala 2 different minor versions of the compiler were free to change the way how they encode different language features in JVM bytecode so each bump of the compiler's minor version resulted in breaking binary compatibility and if a project had any Scala dependencies they all needed to be (cross-)compiled to the same minor Scala version that was used in that project itself. | ||
While in Scala 3 the JVM encoding might still change between minor versions, an additional intermediate format of code representation called TASTy (from `Typed Abstract Syntax Tree`) was introduced. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes it sound like any minor version of Scala 3 might break binary compatibility, but we have no plans to do that so far and won't break it for many years hopefully. Moreover, if/when we do break bincompat, there's a good chance we'll call it Scala 4 to avoid confusion although this isn't settled (cf #10244 (comment)) |
||
|
||
Scala 3.1.2-RC1 adds an experimental `-Yscala-release <release-version>` compiler flag which makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `<release-version>` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this flag enforces emitting TASTy files in an older format ensuring that: | ||
TASTy files are produced from Scala 3 sources during compilation, together with classfiles, and they're normally also included in published artifacts. A Scala compiler in version `3.x1.y1` is able to read TASTy files produced by another compiler in version `3.x2.y2` if `x1 >= x2` (assuming two stable versions of the compiler are considered - `SNAPSHOT` or `NIGHTLY` compiler versions can read TASTy in an older stable format but their TASTY versions are not compatible between each other even if the compilers have the same minor version; also compilers in stable versions cannot read TASTy generated by an unstable version). While having TASTy files in an understandable format on its classpath a Scala 3 compiler can generate bytecode for a project's dependencies on the fly. | ||
|
||
Being able to bump the compiler version in a project without having to wait for all of its dependencies to do the same is already a big leap forward when compared to Scala 2. However, me might still try to do better, especially from the perspective of authors of libraries. | ||
If you maintain a library and you would like it to be usable as a dependency for all Scala 3 projects, you would have to always emit TASTy in a version that would be readble by everyone, which would normally mean getting stuck at 3.0.x forever. | ||
|
||
To solve this problem a new experimental compiler flag `-Yscala-release <release-version>` (available since 3.1.2-RC1) has been added. Setting this flag makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `<release-version>` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this enforces emitting TASTy files in an older format ensuring that: | ||
* the code contains no references to parts of the standard library which were added to the API after `<release-version>` and would crash at runtime when a program is executed with the older version of the standard library on the classpath | ||
* no dependency found on the classpath during compilation (except for the standard library itself) contains TASTy files produced by a compiler newer than `<release-version>` (otherwise they could potentially leak such disallowed references to the standard library) | ||
* no dependency found on the classpath during compilation (except for the standard library itself) contains TASTy files produced by a compiler newer than `<release-version>` (otherwise they could potentially leak such disallowed references to the standard library). | ||
|
||
If any of the checks above is not fulfilled or for any other reason older TASTy cannot be emitted (e.g. the code uses some new language features which cannot be expressed the the older format) the entire compilation fails (with errors reported for each of such issues). | ||
|
||
As this feature is experimental it does not have any special support in build tools yet (at least not in sbt 1.6.1 or lower). | ||
|
@@ -22,4 +28,6 @@ dependencyOverrides ++= Seq( | |
scalaOrganization.value %% "scala3-library" % scalaVersion.value, | ||
scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value // for Scala.js projects | ||
) | ||
``` | ||
``` | ||
|
||
The behaviour of `-Yscala-release` flag might still change in the future, especially it's not guaranteed that every new version of the compiler would be able to generate TASTy in all older formats going back to the one produced by `3.0.x` compiler. |
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 still not a fan of this kind of check, as I said jars can have any names and it'd be weird if we suddenly started imposing undocumented restriction on jar names in some situations.
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 situation where this could potentially fail: if someone uses sbt-assembly to package all their dependencies together and also uses staging, the compiler used at runtime will have only the sbt-assembly jar on its classpath which won't be called scala3-library
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.
Not sure if I understood you correctly. Let's consider a simple scenario.
Project A depends on project B.
Project B is packaged into sbt-assembly jar together with all of its dependencies (including stdlib)
Now we have a few possibilities of what version of the compiler A and B are compiled with:
a) Both A and B use the same minor version - OK
b) A uses 3.x1.y1 without -Yscala-release and B uses 3.x2.y2 if x1 > x2 - OK no matter if B uses -Yscala-release
c) A uses 3.x1.y1 and B uses 3.x2.y2 with
-Yscala-release 3.x1
if x1 < x2 - here we encounter a problem because the fat jar will contain stdlib classes from 3.x2.y2 instead of 3.x1.y1 and 3.x1.y1 compiler won't be able to read them.This isn't specifically a problem of this one check because the problem would still occur if x1 = 0 and x2 = 1. This seems all about the fact that build tools are not aware of -Yscala-release. But still we would need to release this feature as experimental first to let people play with it and then fix the build tools
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 don't have a specific scenario in mind, I just don't think it's wise to have a check like this.
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 really think we should just warn people to not use package scala, it's already dangerous because of package private scala definitions.
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.
(also the reason why we're special casing the standard library here should be documented in the code)
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 opposed to having a filename-based check for now if it avoids accidentally using a 3.1 dependency from a -Yscala-release 3.0 project, but we should treat it as a temporary hack until we have a reliable way of detecting tasty files from the standard library (we could have a flag in the tasty file for that for example).
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.
Actually I thought about adding such kind of a flag to TASTy but for this proper solution we would have to wait until the next minor release. And even now in the worst case our checks would be too strict. So while checking TASTy version we make an exception if we're pretty sure some class comes from the stdlib while checking only the package prefix would allow some other libraries leak references to the newer stdlib API