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

Resolve conflict with JDK15's new isEmpty method on CharSequence #9239

Closed
wants to merge 2 commits into from

Conversation

catap
Copy link

@catap catap commented Oct 11, 2020

java.lang.CharSequence has isEmpty method since JDK 15 that makes impossibly to compile scala by JDK15.

Fix it on the way that allows to compiled with JDK15 and before JDK15.

Fixes scala/bug#12172

@scala-jenkins scala-jenkins added this to the 2.13.4 milestone Oct 11, 2020
@catap catap changed the title Introduced support of JDK15+ [2.13.x] Introduced support of JDK15+ Oct 11, 2020
@catap catap closed this Oct 11, 2020
@catap
Copy link
Author

catap commented Oct 11, 2020

This way allows only to compile but not use it.

Close pull request because it is a wrong and very bad way.

@som-snytt
Copy link
Contributor

OK I'm cancelling the 1-800-flowers. It's a very annoying issue, so the swing is appreciated.

@catap
Copy link
Author

catap commented Oct 11, 2020

@som-snytt I was very optimised about impact.

Right now I doubt that it can be solved.

@SethTisue
Copy link
Member

SethTisue commented Oct 11, 2020

@catap Note that it would have been sufficient to PR the change on 2.11.x, unless nontrivially different code was needed on the different branches. We merge everything forward from 2.11.x -> 2.12.x -> 2.13.x.

@catap
Copy link
Author

catap commented Oct 11, 2020

@SethTisue I can't just cherry-pick it because it created some conflicts => I solved it.

Anyway, after a day of thinking about this bug I see that it should be solved on two levels:

  • inside scala-library by my PRs
  • inside scalac by some hacks

So, I would like to reopen my PRs. Anyway tests are broken and if someone from the team thinks that it shouldn't be used — I'm OK with this.

@catap catap reopened this Oct 11, 2020
@catap catap closed this Oct 11, 2020
@catap catap reopened this Oct 11, 2020
@SethTisue SethTisue marked this pull request as draft October 11, 2020 15:37
@SethTisue SethTisue added prio:blocker release blocker (used only by core team, only near release time) backport candidate java interop release-notes worth highlighting in next release notes labels Oct 11, 2020
@SethTisue SethTisue changed the title [2.13.x] Introduced support of JDK15+ Address conflict with JDK15's new isEmpty method on CharSequence Oct 11, 2020
@catap
Copy link
Author

catap commented Oct 11, 2020

@SethTisue I made the first attempt to force selection of scala isEmpty.

This allows to produce on JDK15 a bytecode that runs on JVM before 15 that is good.

Anyway, I'm a bit concern about my naive approach. I've used termSymbol.fullName == "scala.Predef" as the last filter with.

But I afraid that it costs too much in terms of performance.

If you have something better in mind I'd love to ask some help.

@catap catap changed the title Address conflict with JDK15's new isEmpty method on CharSequence Resolve conflict with JDK15's new isEmpty method on CharSequence Oct 11, 2020
@catap catap force-pushed the jdk15_2.13.x branch 4 times, most recently from f407c0f to 1f68cf8 Compare October 11, 2020 20:13
@catap catap marked this pull request as ready for review October 11, 2020 20:56
@dwijnand
Copy link
Member

So this is the special-case-it-in-the-compiler way. When I heard about this breakage I was thinking we could make the CharSequence extending implicit non-implicit (so binary compatible) and have another implicit class that brings CharSequence's methods without actually extending CharSequence ... except that's not forwards-compatible... 🤔

@catap
Copy link
Author

catap commented Oct 14, 2020

@dwijnand but by this "hack" scala produces the same bytecode that it produces on JDK before 15.

Other approach is broking a lot of compatible in strange places, and here I just help to make right decision :)

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any other way out than compiler support for Array[Char].isEmpty.

src/compiler/scala/tools/nsc/typechecker/Implicits.scala Outdated Show resolved Hide resolved
src/compiler/scala/tools/nsc/typechecker/Implicits.scala Outdated Show resolved Hide resolved
@sjrd
Copy link
Member

sjrd commented Oct 15, 2020

I just thought: what if we add a (intrinsified) macro def isEmpty: Boolean directly in class Array, that would expand x.isEmpty into x.length == 0? As a macro, it wouldn't violate the binary compatibility requirements. And it might be cleaner than special-casing implicit conversion insertion.

@smarter smarter reopened this Oct 27, 2020
@smarter
Copy link
Member

smarter commented Oct 27, 2020

I mean sure, but I have a thousand other things to worry about and all I'm asking is for a decision to be reached soon so I can work with that.

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

Will take a look if I can get it to work, or have to add the stub method to Array.scala.

I don't think there's a clean way to get the synthetic method to work. In order for it to be visible it has to be forced. This works fine except when compiling the standard library using -sourcepath. In this case the Array symbol is has a SourcefileLoader, i.e., a lazy type which completes by sending the source file through the compiler pipeline.

Other synthetic symbols are forced when the Global.Run instance is created, in definitions.init(). Completing a SourcefileLoader this early doesn't work (run.firstPhase is still null). We'd have to find some other hook, ideally somehow add the method right after completing the Array symbol, but there's no good way to do that.

So to me it seems that the best solution is to add the stub to Array.scala.

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

Hmm, that doesn't work so easily either... After adding isEmpty to Array.scala the 2.13.3 compiler can no longer compile the standard library, because calls within the standard library (like clazz.getDeclaredFields.isEmpty) now resolve to the new method, but it's not rewritten by the 2.13.3 compiler. So the backend fails with unexpected array call.

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

My only idea here is to do it in 2 steps:

  1. Add compiler support for a stub isEmpty method in Array.scala
  2. After the next release when we update starr, actually add that method to Array.scala

This means it won't be fixed for 2.13.4, only for 2.13.5. We could use a mergely as starr to avoid that.

I think this is still better than special-casing implicit search.

@catap
Copy link
Author

catap commented Oct 28, 2020

@lrytz am I right that this way is broke compatibility with class file that was compiled with a new standard library and that is using on old one?

Let image that some library is updated a scala version that is used to build it to 2.13.5 and user of this library is using 2.13.3.

If I understand it right the end user will have very wired runtime exception.

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

The Array.class is never used at run-time, Scala arrays are encoded as native arrays in the JVM. A call someArray.isEmpty is translated to someArray.length == 0, which works also when an older Scala library is on the runtime classpath.

@smarter
Copy link
Member

smarter commented Oct 28, 2020

I think this is still better than special-casing implicit search.

This seems a lot more complicated then special-casing implicit search, all for a temporary solution anyway (since the correct solution is to change Predef.scala whenever we can break BC), we've also discussed adding a special case in implicit search like this in dotty at a recent dotty meeting and there wasn't any opposition to it, why do you think it's a worse solution?

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

It's not that temporary, we'd have to keep it forever in 2.13. Adding the method to Array preserves Scala semantics, so for example IntelliJ's typer would understand it. It's also nothing new conceptually, we do the same for the other Array methods.

@smarter
Copy link
Member

smarter commented Oct 28, 2020

so for example IntelliJ's typer would understand it.

That would need to be tested, I don't know how their typer deals with macro methods but I wouldn't be surprised if something breaks.

After the next release when we update starr, actually add that method to Array.scala

Can already prepare a PR with that change so I can test dotty against the updated Array.scala?

@smarter
Copy link
Member

smarter commented Oct 28, 2020

Also what prevents you from using an intermediate starr based on a nightly instead of waiting for ~3-4 months?

@SethTisue
Copy link
Member

re-STARRing on a mergely seems quite acceptable to me, we've done it a number of times before

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

Also what prevents you from using an intermediate starr based on a nightly instead of waiting for ~3-4 months?

I mentioned that as an option above. We only do that when really necessary, I'd be fine doing it here.

I'm not sure if a nightly on our artifactory is OK or if we should publish something to maven central, for reproducability.

@SethTisue
Copy link
Member

SethTisue commented Oct 28, 2020

Historically we have done it to Maven Central. I see no downside.

@lrytz
Copy link
Member

lrytz commented Oct 28, 2020

Testing the follow up after restarr here: #9288

@smarter
Copy link
Member

smarter commented Oct 29, 2020

Another approach which could be considered would be to simply make ArrayCharSequence not implicit at the cost of breaking source-compat in hopefully rare cases (but not binary compat):

diff --git src/library/scala/Predef.scala src/library/scala/Predef.scala
index c9911f9299..de2b8984a8 100644
--- src/library/scala/Predef.scala
+++ src/library/scala/Predef.scala
@@ -388,12 +388,13 @@ object Predef extends LowPriorityImplicits {
   }

   /** @group implicit-classes-char */
-  implicit final class ArrayCharSequence(arrayOfChars: Array[Char]) extends CharSequence {
+  final class ArrayCharSequence(arrayOfChars: Array[Char]) extends CharSequence {
     def length: Int                                     = arrayOfChars.length
     def charAt(index: Int): Char                        = arrayOfChars(index)
     def subSequence(start: Int, end: Int): CharSequence = new runtime.ArrayCharSequence(arrayOfChars, start, end)
     override def toString                               = arrayOfChars.mkString
   }
+  def ArrayCharSequence(arrayOfChars: Array[Char]): ArrayCharSequence = new ArrayCharSequence(arrayOfChars)

   /** @group conversions-string */
   @inline implicit def augmentString(x: String): StringOps = new StringOps(x)

That's one less implicit conversion in the world which would be a good thing, and for people who really want a CharSequence backed by their Array[Char], the Internet tells me that can be easily accomplished using java.nio.CharBuffer.wrap. The transition can be made smoother by first deprecating ArrayCharSequence in 2.13.4 with a message mentioning the alternative, and then removing the implicit keyword in 2.13.5 (this is the approach I took in scalacheck for a similar issue: typelevel/scalacheck#498)

@dwijnand
Copy link
Member

Yes! So by giving it the same name it's not forward binary incompatible. Of course... -.-

I'm happy with dropping source compatibility for implicitly enriching Array[Char].

@sjrd
Copy link
Member

sjrd commented Oct 29, 2020

That does seem like a reasonable option, indeed.

@lrytz
Copy link
Member

lrytz commented Oct 29, 2020

Intriguing idea... Should we then also deprecate SeqCharSequence?

I looked a bit through the history

I guess the main question is how much code we'd break. The community build can tell something about that.

@dwijnand
Copy link
Member

Alt: #9292

@dwijnand
Copy link
Member

dwijnand commented Nov 4, 2020

Went with #9292 instead.

Thank you, @catap, for jumping on this so quickly. Although we didn't end up merging your fix, this was really helpful to think through different approaches. ❤️

@dwijnand dwijnand closed this Nov 4, 2020
@dwijnand dwijnand removed this from the 2.13.4 milestone Nov 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport candidate java interop prio:blocker release blocker (used only by core team, only near release time) release-notes worth highlighting in next release notes
Projects
None yet
8 participants