From 0230f94fa975cd7e554ff2e2225bbbffb0eb6830 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 29 Oct 2020 09:39:09 +0000 Subject: [PATCH] Make the CharSequence wrappers in Predef non-implicit, for JDK 15 In JDK 15 CharSequence has an isEmpty method with a default implementation, which clashes with our Array[Char]#isEmpty, IndexedSeq[Char]#isEmpty, as well as our StringBuilder and reflect's Name. --- src/library/scala/Predef.scala | 19 ++++++++++----- .../collection/mutable/StringBuilder.scala | 10 ++++++-- .../scala/reflect/internal/Names.scala | 10 ++++++-- test/junit/scala/ArrayTest.scala | 23 ++++++++++++++++++- .../scala/CharSequenceImplicitsTests.scala | 16 ------------- 5 files changed, 51 insertions(+), 27 deletions(-) delete mode 100644 test/junit/scala/CharSequenceImplicitsTests.scala diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index c9911f92994f..56b60005c19c 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -87,9 +87,9 @@ import scala.annotation.meta.{ companionClass, companionMethod } * @groupprio implicit-classes-any 70 * @groupdesc implicit-classes-any These implicit classes add useful extension methods to every type. * - * @groupname implicit-classes-char CharSequence Conversions - * @groupprio implicit-classes-char 80 - * @groupdesc implicit-classes-char These implicit classes add CharSequence methods to Array[Char] and IndexedSeq[Char] instances. + * @groupname char-sequence-wrappers CharSequence Wrappers + * @groupprio char-sequence-wrappers 80 + * @groupdesc char-sequence-wrappers Wrappers that implements CharSequence and were implicit classes. * * @groupname conversions-java-to-anyval Java to Scala * @groupprio conversions-java-to-anyval 90 @@ -380,21 +380,28 @@ object Predef extends LowPriorityImplicits { def +(other: String): String = String.valueOf(self) + other } - implicit final class SeqCharSequence(sequenceOfChars: scala.collection.IndexedSeq[Char]) extends CharSequence { + /** @group char-sequence-wrappers */ + final class SeqCharSequence(sequenceOfChars: scala.collection.IndexedSeq[Char]) extends CharSequence { def length: Int = sequenceOfChars.length def charAt(index: Int): Char = sequenceOfChars(index) def subSequence(start: Int, end: Int): CharSequence = new SeqCharSequence(sequenceOfChars.slice(start, end)) override def toString = sequenceOfChars.mkString } - /** @group implicit-classes-char */ - implicit final class ArrayCharSequence(arrayOfChars: Array[Char]) extends CharSequence { + /** @group char-sequence-wrappers */ + def SeqCharSequence(sequenceOfChars: scala.collection.IndexedSeq[Char]): SeqCharSequence = new SeqCharSequence(sequenceOfChars) + + /** @group char-sequence-wrappers */ + 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 } + /** @group char-sequence-wrappers */ + def ArrayCharSequence(arrayOfChars: Array[Char]): ArrayCharSequence = new ArrayCharSequence(arrayOfChars) + /** @group conversions-string */ @inline implicit def augmentString(x: String): StringOps = new StringOps(x) diff --git a/src/library/scala/collection/mutable/StringBuilder.scala b/src/library/scala/collection/mutable/StringBuilder.scala index 99421c45e8ed..cadeb09a8d98 100644 --- a/src/library/scala/collection/mutable/StringBuilder.scala +++ b/src/library/scala/collection/mutable/StringBuilder.scala @@ -12,8 +12,6 @@ package scala.collection.mutable -import java.lang.String - import scala.collection.{IterableFactoryDefaults, IterableOnce} import scala.collection.immutable.WrappedString @@ -468,6 +466,14 @@ final class StringBuilder(val underlying: java.lang.StringBuilder) extends Abstr * @return the last applicable index where target occurs, or -1 if not found. */ def lastIndexOf(str: String, fromIndex: Int): Int = underlying.lastIndexOf(str, fromIndex) + + /** Tests whether this builder is empty. + * + * This method is required for JDK15+ compatibility + * + * @return `true` if this builder contains nothing, `false` otherwise. + */ + override def isEmpty: Boolean = underlying.length() == 0 } object StringBuilder { diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala index b557c4013886..0a127e47920a 100644 --- a/src/reflect/scala/reflect/internal/Names.scala +++ b/src/reflect/scala/reflect/internal/Names.scala @@ -187,12 +187,17 @@ trait Names extends api.Names { // Classes ---------------------------------------------------------------------- + // Dummy trait to make Name#isEmpty with override keyword at JDK before 15 + sealed trait NameHasIsEmpty { + def isEmpty: Boolean + } + /** The name class. * TODO - resolve schizophrenia regarding whether to treat Names as Strings * or Strings as Names. Give names the key functions the absence of which * make people want Strings all the time. */ - sealed abstract class Name(protected val index: Int, protected val len: Int, protected val cachedString: String) extends NameApi with CharSequence { + sealed abstract class Name(protected val index: Int, protected val len: Int, protected val cachedString: String) extends NameApi with NameHasIsEmpty with CharSequence { type ThisNameType >: Null <: Name protected[this] def thisName: ThisNameType @@ -208,8 +213,9 @@ trait Names extends api.Names { /** The length of this name. */ final def length: Int = len - final def isEmpty = length == 0 final def nonEmpty = !isEmpty + // This method is implements NameHasIsEmpty, and overrides CharSequence's isEmpty on JDK 15+ + override final def isEmpty = length == 0 def nameKind: String def isTermName: Boolean diff --git a/test/junit/scala/ArrayTest.scala b/test/junit/scala/ArrayTest.scala index 753c485a69f5..7d3f3e3de9ce 100644 --- a/test/junit/scala/ArrayTest.scala +++ b/test/junit/scala/ArrayTest.scala @@ -1,6 +1,6 @@ package scala -import org.junit.Assert.assertArrayEquals +import org.junit.Assert.{ assertArrayEquals, assertFalse, assertTrue } import org.junit.Test import scala.runtime.BoxedUnit @@ -13,4 +13,25 @@ class ArrayTest { assertArrayEquals(expected, Array.copyAs[Unit](Array[Nothing](), 32).asInstanceOf[Array[AnyRef]]) assertArrayEquals(expected, Array.copyAs[Unit](Array[Unit](), 32).asInstanceOf[Array[AnyRef]]) } + + @Test + def testArrayIsEmpty(): Unit = { + assertTrue(Array[Int]().isEmpty) + assertTrue(Array[Char]().isEmpty) // scala/bug#12172 + assertTrue(Array[String]().isEmpty) + + assertFalse(Array(1).isEmpty) + assertFalse(Array[Char](1).isEmpty) + assertFalse(Array("").isEmpty) + + def ge[T](a: Array[T]) = a.isEmpty + + assertTrue(ge(Array[Int]())) + assertTrue(ge(Array[Char]())) + assertTrue(ge(Array[String]())) + + assertFalse(ge(Array(1))) + assertFalse(ge(Array[Char]('x'))) + assertFalse(ge(Array(""))) + } } diff --git a/test/junit/scala/CharSequenceImplicitsTests.scala b/test/junit/scala/CharSequenceImplicitsTests.scala deleted file mode 100644 index a0aacefdf0d1..000000000000 --- a/test/junit/scala/CharSequenceImplicitsTests.scala +++ /dev/null @@ -1,16 +0,0 @@ -package scala - -import org.junit.Test -import org.junit.Assert._ -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -// these two implicit conversions come from Predef - -@RunWith(classOf[JUnit4]) -class CharSequenceImplicitsTests { - @Test def arrayAsCharSequence(): Unit = - assertEquals("ab", (Array ('a', 'b'): CharSequence).toString) - @Test def indexedSeqAsCharSequence(): Unit = - assertEquals("ab", (IndexedSeq('a', 'b'): CharSequence).toString) -}