-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Binary versions in the Scala 3.x era #10244
Comments
I like 2.13 and 3, because it's ambitious. However it makes it impossible for a library to be cross-built for 2.13 and 3, so a library must decide if it's publishing for one or for the other... Riffing off your ideas, I kind of like "3 and 3.T" but where "T" is literally "the Scala 3 Tasty" binary version (not, for example "3.6"). It's also mirrors OnePlus's phone names (3T, 5T, etc). |
One possible problem might be that we want to allow backward compatibility between minor versions. So if 3.0 is binary 2.13, what is 3.1? It can't be 2.14 because that would imply a global break according to the Scala-2 scheme and it can't be 2.13.x either since that would imply backward and forward compatibility. Do I understand that correctly? |
It would be This scheme has worked without any hiccup for Scala.js 0.6.x and 1.x: we've broken forward binary compatibility many times during the 0.6.x cycle, and already 3 times during the 1.x cycle. But 0.6.x has always used the binary version |
I think using I think all the other suggestions are fine, but I find using |
But I have thought that for Scala 2.x, it's a stricter rule that also requires forward compatibility? |
Maintainers might want that freedom, but if they actually use it for anything non-trivial (i.e., where the binary API is not 100% the same), they might prevent the downward ecosystem from actually leveraging the bidirectional interop we built between Scala 2.13 and 3. Because an application A might use libraries X (2.13) and Y (3), both depending on the upstream library L which has variants for 2.13 and 3. And then A ends up with a non-reconcilable classpath. |
That is not an issue, because it only applies to a) the binary encoding produced by the compiler, which is anyway frozen and b) the binary API of scala-library.jar, which is frozen as well. It doesn't apply to other libraries, in particular not |
My two cents… From a user's perspective, the question to be answered is, given their set Note that we don't need to answer the inverse question: for a given bincompat suffix, enumerate all compatible Scala versions. That's an interesting question, but one that never, in practice, arises. This asymmetry suggests that the For all versions of Scala 3 prior to The caveat here is that build tools need to understand this cliff so as to properly warn users that Note that Scala actually went through this kind of transition once before. I can't remember exactly when the shift took place (I believe it was during the 2.8.x line), but way back in 2.7.x days the bincompat suffix included the full Scala version since compatibility was broken with absolutely every release. It wasn't until later that Scala's own compatibility guarantees were hardened such that libraries could publish once for a What is going to happen with Tasty is quite analogous to this compatibility hardening nearly a decade ago, except the minor component is now being dropped. Also to @smarter's point, I would discourage everyone from thinking of the 3.0 and 2.13 ecosystems as being merged. They really are not. There are several reasons why this is the case, but very briefly:
Tldr, |
I disagree. The real question is: given a full version (e.g.,
IIUC your comment, that would mean that Scala 3.1.0 would use binary suffix
There will be at least one cliff regardless of the version scheme we use. But I agree with you that we can teach build tools to handle the cliffs. That's not an issue we need to be concerned about.
That would be solved by using
I don't think so. Only roadblocks that surface in the public/protected API of the library are forced on downstream users. But I would certainly hope that those an overwhelming minority, compared to roadblocks that impact the implementation of the library (in particular inside methods). Exploiting bugs in nsc is often inside methods (pattern matching unsoundness for example), not so much in the APIs. And I may be wrong but I don't think it's even possible for a skolem to be part of a public API, is it? You maintain more libraries with complicated stuff than I do, so maybe I'm wrong on all accounts, here.
We should definitely fix issues. I may come off as defensive here. We've worked hard on designs and implementations to make this two-way interoperability between Scala 2.13 and 3. We should capitalize on it unless there is strong evidence that it's broken. |
Unless I'm misunderstanding, the 2.13 & 3 compatibility does not support macros, which then forces many libraries to publish for both 2.13 and 3. Current list of macro libraries: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html |
A library that has macros can put both the Scala 2 and Scala 3 macros in the same artifact if it is compiled by Scala 3. |
@sjrd Good to hear. Is that supported by SBT yet? |
Yes, in the sense that sbt has nothing to do about it. It's supported by the compilers. |
Understood, though how would an SBT project be setup to generate a Scala 3 artifact with both 2.13 and 3 macros included? As of now, the community is generally using |
Nevermind, found it in "Mixing Macro Definitions" section of https://scalacenter.github.io/scala-3-migration-guide/docs/macros/migration-tutorial.html |
So we can think of different criteria for compatibility. "Can we swap JARs?"-compatibilityIf I published an sbt plugin using 1.0.0, the plugin should work today using sbt 1.4.2. Can we resolve the diamond simply by evicting lower version numbers from the either side? "Can we compile together?"-compatibilityFor Scala 3.0.0, and Scala 3.1.0 even, it seems like we will be able to compile together with either The point where we reach we can't compile against anything is "Can we link together?"-compatibility
_tsy0.1_2.13
If we can learn from recent Java, it's that we don't have to be too precious about the first segment of the version number. I don't think we should quite ape their neck breaking pace, but calling Today, the underscored suffix When Scala 4.0.0 comes out, we can call it The benefit of this approach is that we don't have to handle cliffs. We create a convention instead. |
Only if they're otherwise identical, which I think will rarely be the case (see below).
I'm not sure how this can work. For a project I'm working on I have the following situation.
Plus distinct sets of implicit syntax to paper over the resulting API differences. How would this work in a single artifact? I want to emphasize that this is not a complex project. I have intentionally kept it very simple. I think pure center-lane Scala libraries are a bit of a myth and the common case is going to be more like mine than not. |
Here are some of the issues I can see with us using
This is all just off the top of my mind, at no point in time did we develop Dotty with this goal in mind, there's likely to be many unknown unknowns. If we want to pursue this, I expect that it will require delaying the Scala 3.0 release by at least 6 months so we can get some confidence that this could work. But I really don't see the point of doing that since in the end this will likely just heavily discourage library authors from using Scala 3 at all in their projects: what's the point if you can't use Scala 3 features anyway but get new bugs? |
With all those differences in your API, can there even be user code that uses that library with the different Scala versions and the same code!? To me it seems like those differences are big enough that it is a different version of the library.
You'd cross-compile the tests.
Same as above: if you have a different API for Scala 3, it's a different version of the library to begin with, because you can write user code that works with both. Cross-compilation is a moot point if you have a different version of the library. |
I see this as exactly analogous to current 2.x version shuffles, where we do what we can to provide a source-compatible upgrade path, it's just more involved when moving to 3.x. This is what the community is doing now, and if you're advocating for forks in these cases then I think it moves very much in the opposite direction of your intent; the 2.x and 3.x worlds will be almost entirely disjoint and it puts a gigantic burden on library developers. |
I think it's more like there are libraries that will be consumable by both 2.13 and 3 ("that support both 2.13 and 3") and libraries that will drop 2.13 and use 3-only features. The problem is that you can't tell. But this problem doesn't come now, with the "let's reuse _2.13" idea, it's already present with the Tasty Reader supporting some unspecified subset of Scala 3 and their artefacts being indistinguishable by those that aren't consumable. And vice-versa Dotty can consume _2.13... except if it has those Scala 2 only features (but I believe these are small, fringe drops). |
If you have to explicitly put in your build.sbt |
@sjrd I don't think we're super far apart on what this should be, just a difference in optimism surrounding interface compatibility. We should try to have a sync-up on this topic.
(emphasis mine) This is exactly what I'm saying. More specifically, I'm saying that this is quite common. The problems that I've seen tend to fall in the following areas:
The first area is the kind of pain felt by scodec, circe-generic, skunk, etc. Hopefully it's obvious why this is an issue. I don't know that anyone has actually tested the "compile Scala 2 macro code on Scala 3" route, since we didn't really know that existed until just now, so I can't speak to its viability. Given the nature of some of this macro code though (e.g. Regarding GADTs, Scala 2 is just very buggy in this area and I think we all understand that. It's much better than it was years and years ago, but still rough. It's very common to use various tricks to work around these kinds of problems, usually involving using implicits to guide type inference. These tricks change pretty dramatically in Scala 3; many are now unnecessary, while others were always unsound and will simply never work. Also skipping ahead a bit, the interaction between GADTs and variance is a particularly troublesome point. And again, this is all in the public APIs; these aren't implementation details. Looping back to implicit resolution… Scala 3's implicit search space is very slightly different than Scala 2's. In practice, this seems to most often be caused by situations where Scala 3 correctly identifies subtyping relations which Scala 2 simply ignored for unknown reasons. These additional relations open up the search space slightly further, since additional companions are in scope and such. This again is a thing which is very visible to users, since implicit search is performed in their code. Finally, type inference is often similar, but not always, particularly when polymorphic variables are being instantiated via implicit search. There's a great example of this in Cats where a seemingly-inoccuous type signature simply does not work on Scala 3 because an In my experience, migration issues within method bodies are quite rare unless you're doing something patently weird (like Taking this even further, the eviction issue that @tpolecat and @eed3si9n discussed above means that even one or two libraries being in this kind of situation forces all libraries to cross-publish, because you can't really mix the ecosystems without liberal use of It doesn't really remove the danger though. A library author must be absolutely sure that they aren't hitting any of these Scala 2/3 incompatibility cases before they can recommend Anyway, this all probably sounds more dire than it actually is. I want to clarify that I think that at worst the 2.13 -> 3.0 migration is about as hard as a typical 2.x -> 2.y migration. I just don't believe that it's any easier, and playing tricks with the bincompat suffixes to try to make it seem like it's easier is just going to tie library authors' hands. At any rate, let's have a discussion on all of this. |
I've been using the term Scala 2.13-3.x sandwich, and maybe we can collectively agree on what components this sandwich actually admits, so we are neither over-promising or pessimistic. If we accept that the bottom bread must be Scala 2.13 and only the stuffs that do not transitively depend on 3.x cross built libraries like Cats, then we can say that |
I haven't really had time to process all the feedback yet. I will come back for that. I'm replying now because it has been brought to my attention that some people felt this was a done deal and started panicking. It's not. The issue is an open question with several options, some pros and cons, and it is very much looking for feedback from anyone involved. There was no internal discussion prior to opening this issue, other than "We need to figure out what the binary versions will be after 3.0.0. Can someone open an issue to discuss it?" and I said I would open an issue. I apologize if people took this the wrong way. |
Fast forward after the offline discussions we had:
|
Until Dotty 0.x.y-RC1's, the binary and TASTy formats were free to be broken at any time, and hence the binary version was
0.x
(for example0.27
). With 3.0.0-M1, it has become3.0.0-M1
, which corresponds to what the milestones and RCs of Scala 2 do (a full version number).We need to decide what will happen in Scala 3.0.0 and onwards. There are a few things to take into account.
3.T.0
at which we will break and finalize TASTy, as well as change the stdlib to use Scala 3 features in appropriate places. That implies that it will also break binary compatibility.Therefore, we have at least two binary versions to settle on: the one for < 3.T.0, and the one for >= 3.T.0. I see the following options:
3.0
and3.T
. That choice means: the first minor version that uses the new binary version. It has the benefit that it can develop further in time if we decide that binary versions should stick to binary compatibility, not to TASTy compatibility, even after 3.T.0, since we can introduce another3.U
at some point. It is however confusing that3.1
is compatible with binary version3.0
but not3.T
.3.0
and3
: That choice bets on 3 being the ultimate binary version, which will never change again. The use of3.0
is a transient state in the meantime. It will be weird that3.0.0
cannot read3
, but that's not too bad.3
and4
: After all, there is no hard requirement for the binary version to be related to the general version. That choice makes it explicit that a binary version is not structured. It's flat: when it changes, the whole ecosystem has to be rebuilt, no matter what. (If we preserved backward compatibility, we wouldn't change the binary version to begin with.)2.13
and3
. This one will make people scream, but it makes perfect sense given that Scala 3 and 2.13 are forward and backward binary compatible. The ecosystems are therefore not split: it is the same ecosystem. That also makeswithDottyCompat
and the reverse one for which we don't have a name yet both moot. It does mean that a library cannot release the same version separately for 2.13 and 3. But after all, it shouldn't have to, since we made everything so that the binary built by one version is usable from the other version.The text was updated successfully, but these errors were encountered: