Skip to content
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

Enable generation of TASTy files readable for older compilers #14156

Merged
merged 22 commits into from Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a398130
Run test groups with different compiler versions
Kordyjan Oct 28, 2021
14719cf
Add -scala-release flag
Kordyjan Nov 8, 2021
99e0cc2
Output specified tasty version
Kordyjan Nov 8, 2021
1c17ea8
Handle classpath substitution in classpath
Kordyjan Nov 9, 2021
1455c4b
Fix pickler tests
Kordyjan Nov 10, 2021
22e8f8f
Change `-scala-release` flag into proper VersionSetting
Kordyjan Nov 15, 2021
ab3efcf
Add `since` annotation
Kordyjan Nov 16, 2021
44ffb68
Add since annotations to all symbols added in 3.1
Kordyjan Nov 16, 2021
f75c5ee
Restrict scope of `since` annotation
Kordyjan Nov 16, 2021
f1bae32
Check for types with `since` annotation
Kordyjan Nov 17, 2021
465ef4c
wip
Kordyjan Nov 22, 2021
42160dc
Finalize work on @since and -scala-release:
prolativ Dec 20, 2021
46b59c9
Reworks for forward compat:
prolativ Dec 28, 2021
72cb484
Use a more portable way to download an old compiler for tests
prolativ Dec 29, 2021
52f0177
scala-release: Adjust MiMa filters; Revert changes to tasty module
prolativ Dec 29, 2021
e39b618
-Yscala-release support: extend community build with basic forward-co…
prolativ Jan 12, 2022
e1f00c8
-Yscala-release support: test cats-effect compiled with scala 3.0.2 w…
prolativ Jan 13, 2022
f2aede6
-Yscala-release support: Improve caching artifacts downloaded for tests
prolativ Jan 14, 2022
3f88de4
-Yscala-release support: temporarily enable forward compat community …
prolativ Jan 14, 2022
a86db05
-Yscala-release support: checking if classes come from stdlib based o…
prolativ Jan 17, 2022
0c8ac84
-Yscala-release support: checking if classes come from stdlib based o…
prolativ Jan 18, 2022
85cf9be
-Yscala-release support: fix documentation; run extended CI only for …
prolativ Jan 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -601,7 +601,7 @@ object projects:
dependencies = () => List(cats, disciplineMunit)
)

lazy val catsMtlForwardCompat = catsMtl.forwardCompat.withScalaRelease("3.0")
lazy val catsMtlForwardCompat = catsMtl.forwardCompat.copy(compilerVersion = "3.0.2")

lazy val coop = SbtCommunityProject(
project = "coop",
Expand Down
Expand Up @@ -972,7 +972,7 @@ class ClassfileParser(
def isStdlibClass(cls: ClassDenotation): Boolean =
ctx.platform.classPath.findClassFile(cls.fullName.mangledString) match {
case Some(entry: ZipArchive#Entry) =>
entry.underlyingSource.map(_.name.startsWith("scala3-library_3-")).getOrElse(false)
entry.underlyingSource.map(_.name.startsWith("scala3-library_")).getOrElse(false)
Copy link
Member

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.

Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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)

Copy link
Member

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).

Copy link
Contributor Author

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

case _ => false
}
val isTastyCompatible = fileTastyVersion.isCompatibleWith(ctx.tastyVersion) || isStdlibClass(classRoot)
Expand Down
18 changes: 13 additions & 5 deletions docs/docs/reference/language-versions/binary-compatibility.md
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The 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).
Expand All @@ -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.