Skip to content

Commit 06a5552

Browse files
committed
Replace runtime.Rich* implicits on primitives by direct extension methods.
It has always been weird that the public API of primitive types, enriched by `Predef` depends on the `Rich*` classes, which are in the not-really-public `runtime` package. Moreover, the design of those classes was overabstracted, with consequences on performance (unnecessary boxing) and quality of the API (including methods that do not make sense on some types). To mitigate that, the individual `Rich*` classes redefined some (but not all) of the methods, defeating the abstraction. We solve both issues with a simple solution: define all those methods as simple `extension` methods. We do this directly in the companion objects of the primitive types.
1 parent ce57b71 commit 06a5552

23 files changed

+870
-59
lines changed

library/src/scala/Boolean.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,27 @@ object Boolean extends AnyValCompanion {
138138
/** The String representation of the scala.Boolean companion object. */
139139
override def toString = "object scala.Boolean"
140140

141-
}
141+
extension (self: Boolean) {
142+
143+
/** Compares `this` to `that` according to the standard total ordering.
144+
*
145+
* Returns:
146+
* - a positive value if `this` is `true` and `that` is `false`
147+
* - a negative value if `this` is `false` and `that` is `true`
148+
* - `0` if `this == that`
149+
*/
150+
def compare(that: Boolean): Int = java.lang.Boolean.compare(self, that)
151+
152+
/** Returns true iff `this` is `false` and `that` is `true`. */
153+
def <(that: Boolean): Boolean = !self & that
142154

155+
/** Returns true iff `this` is `true` and `that` is `false`. */
156+
def >(that: Boolean): Boolean = self & !that
157+
158+
/** Returns true iff `this` is `false` or `that` is `true`. */
159+
def <=(that: Boolean): Boolean = !self | that
160+
161+
/** Returns true iff `this` is `true` or `that` is `false`. */
162+
def >=(that: Boolean): Boolean = self | !that
163+
}
164+
}

library/src/scala/Byte.scala

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package scala
1818

1919
import scala.language.`2.13`
2020

21+
import scala.collection.immutable.NumericRange
22+
2123
/** `Byte`, a 8-bit signed integer (equivalent to Java's `byte` primitive type) is a
2224
* subtype of [[scala.AnyVal]]. Instances of `Byte` are not
2325
* represented by an object in the underlying runtime system.
@@ -485,5 +487,118 @@ object Byte extends AnyValCompanion {
485487
implicit def byte2long(x: Byte): Long = x.toLong
486488
implicit def byte2float(x: Byte): Float = x.toFloat
487489
implicit def byte2double(x: Byte): Double = x.toDouble
488-
}
489490

491+
extension (self: Byte) {
492+
/** Returns `'''true'''` if this number has no decimal component.
493+
* Always `'''true'''` for `Byte`.
494+
*/
495+
@deprecated("isWhole on Byte is always true", "2.12.15")
496+
def isWhole: Boolean = true
497+
498+
/** Returns `true` iff this is within the
499+
* range of [[scala.Char]] MinValue and MaxValue; otherwise returns `false`.
500+
*/
501+
def isValidChar: Boolean = self >= 0
502+
503+
/** Returns `true` iff this is within the
504+
* range of [[scala.Byte]] MinValue and MaxValue; otherwise returns `false`.
505+
*/
506+
@deprecated("isValidByte on Byte is always true", "3.8.0")
507+
def isValidByte: Boolean = true
508+
509+
/** Returns `true` iff this is within the
510+
* range of [[scala.Short]] MinValue and MaxValue; otherwise returns `false`.
511+
*/
512+
@deprecated("isValidShort on Byte is always true", "3.8.0")
513+
def isValidShort: Boolean = true
514+
515+
/** Returns `true` iff this is within the
516+
* range of [[scala.Int]] MinValue and MaxValue; otherwise returns `false`.
517+
*/
518+
@deprecated("isValidInt on Byte is always true", "3.8.0")
519+
def isValidInt: Boolean = true
520+
521+
/** Returns the absolute value of `this`. */
522+
def abs: Byte = java.lang.Math.abs(self.toInt).toByte
523+
524+
/** Returns `this` if `this > that` or `that` otherwise. */
525+
def max(that: Byte): Byte = java.lang.Math.max(self.toInt, that.toInt).toByte
526+
527+
/** Returns `this` if `this < that` or `that` otherwise. */
528+
def min(that: Byte): Byte = java.lang.Math.min(self.toInt, that.toInt).toByte
529+
530+
/** Returns the sign of `this`.
531+
*
532+
* `0` if `this == 0`, `-1` if `this < 0` and `1` if `this > 0`.
533+
*/
534+
def sign: Byte = java.lang.Integer.signum(self.toInt).toByte
535+
536+
/** Returns the signum of `this`. */
537+
@deprecated("use `sign` method instead", since = "2.13.0")
538+
def signum: Int = self.sign.toInt
539+
540+
/** Compares `this` to `that` according to the standard total ordering.
541+
*
542+
* Returns:
543+
* - a positive value if `this > that`
544+
* - a negative value if `this < that`
545+
* - `0` if `this == that`
546+
*/
547+
def compare(that: Byte): Int = java.lang.Byte.compare(self, that)
548+
549+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to but not including `end`.
550+
*
551+
* @param end The final bound of the range to make.
552+
*/
553+
def until(end: Byte): NumericRange.Exclusive[Byte] = NumericRange(self, end, 1)
554+
555+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to but not including `end`.
556+
*
557+
* @param end The final bound of the range to make.
558+
* @param step The number to increase by for each step of the range.
559+
*/
560+
def until(end: Byte, step: Byte): NumericRange.Exclusive[Byte] = NumericRange(self, end, step)
561+
562+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to and including `end`.
563+
*
564+
* @param end The final bound of the range to make.
565+
*/
566+
def to(end: Byte): NumericRange.Inclusive[Byte] = NumericRange.inclusive(self, end, 1)
567+
568+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to and including `end`.
569+
*
570+
* @param end The final bound of the range to make.
571+
* @param step The number to increase by for each step of the range.
572+
*/
573+
def to(end: Byte, step: Byte): NumericRange.Inclusive[Byte] = NumericRange.inclusive(self, end, step)
574+
575+
// ------------------------------------
576+
// For source compatibility with the previous API, when the rhs is an Int, we want an int Range
577+
578+
/** A [[scala.collection.immutable.Range]] from `this` up to but not including `end`.
579+
*
580+
* @param end The final bound of the range to make.
581+
*/
582+
def until(end: Int): Range = Range(self.toInt, end, 1)
583+
584+
/** A [[scala.collection.immutable.Range]] from `this` up to but not including `end`.
585+
*
586+
* @param end The final bound of the range to make.
587+
* @param step The number to increase by for each step of the range.
588+
*/
589+
def until(end: Int, step: Int): Range = Range(self.toInt, end, step)
590+
591+
/** A [[scala.collection.immutable.Range]] from `this` up to and including `end`.
592+
*
593+
* @param end The final bound of the range to make.
594+
*/
595+
def to(end: Int): Range.Inclusive = Range.inclusive(self.toInt, end, 1)
596+
597+
/** A [[scala.collection.immutable.Range]] from `this` up to and including `end`.
598+
*
599+
* @param end The final bound of the range to make.
600+
* @param step The number to increase by for each step of the range.
601+
*/
602+
def to(end: Int, step: Int): Range.Inclusive = Range.inclusive(self.toInt, end, step)
603+
}
604+
}

library/src/scala/Char.scala

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package scala
1818

1919
import scala.language.`2.13`
2020

21+
import scala.collection.immutable.NumericRange
22+
2123
/** `Char`, a 16-bit unsigned integer (equivalent to Java's `char` primitive type) is a
2224
* subtype of [[scala.AnyVal]]. Instances of `Char` are not
2325
* represented by an object in the underlying runtime system.
@@ -484,5 +486,154 @@ object Char extends AnyValCompanion {
484486
implicit def char2long(x: Char): Long = x.toLong
485487
implicit def char2float(x: Char): Float = x.toFloat
486488
implicit def char2double(x: Char): Double = x.toDouble
487-
}
488489

490+
extension (self: Char) {
491+
492+
/** Returns `'''true'''` if this number has no decimal component.
493+
* Always `'''true'''` for `RichInt`.
494+
*/
495+
@deprecated("isWhole on Char is always true", "2.12.15")
496+
def isWhole: Boolean = true
497+
498+
/** Returns `true` iff this is within the
499+
* range of [[scala.Char]] MinValue and MaxValue; otherwise returns `false`.
500+
*/
501+
@deprecated("isValidChar on Char is always true", "3.8.0")
502+
def isValidChar: Boolean = true
503+
504+
/** Returns `true` iff this is within the
505+
* range of [[scala.Byte]] MinValue and MaxValue; otherwise returns `false`.
506+
*/
507+
def isValidByte: Boolean = self.toInt <= Byte.MaxValue.toInt
508+
509+
/** Returns `true` iff this is within the
510+
* range of [[scala.Short]] MinValue and MaxValue; otherwise returns `false`.
511+
*/
512+
def isValidShort: Boolean = self.toInt <= Short.MaxValue.toInt
513+
514+
/** Returns `true` iff this is within the
515+
* range of [[scala.Int]] MinValue and MaxValue; otherwise returns `false`.
516+
*/
517+
@deprecated("isValidInt on Char is always true", "3.8.0")
518+
def isValidInt: Boolean = true
519+
520+
/** Returns the absolute value of `this`. */
521+
@deprecated("Char's are never negative; abs is redundant and can be removed", since = "3.8.0")
522+
def abs: Char = self
523+
524+
/** Returns `this` if `this > that` or `that` otherwise. */
525+
def max(that: Char): Char = java.lang.Math.max(self.toInt, that.toInt).toChar
526+
527+
/** Returns `this` if `this < that` or `that` otherwise. */
528+
def min(that: Char): Char = java.lang.Math.min(self.toInt, that.toInt).toChar
529+
530+
/** Returns the sign of `this`.
531+
*
532+
* zero if the argument is zero, -zero if the argument is -zero,
533+
* one if the argument is greater than zero, -one if the argument is less than zero,
534+
* and NaN if the argument is NaN where applicable.
535+
*/
536+
@deprecated("since Char's are never negative, compare to '\\u0000' instead", since = "3.8.0")
537+
def sign: Char = java.lang.Integer.signum(self.toInt).toChar
538+
539+
/** Returns the signum of `this`. */
540+
@deprecated("use `sign` method instead", since = "2.13.0")
541+
def signum: Int = self.sign
542+
543+
/** Compares `this` to `that` according to the standard total ordering.
544+
*
545+
* Returns:
546+
* - a positive value if `this > that`
547+
* - a negative value if `this < that`
548+
* - `0` if `this == that`
549+
*/
550+
def compare(that: Char): Int = java.lang.Character.compare(self, that)
551+
552+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to but not including `end`.
553+
*
554+
* @param end The final bound of the range to make.
555+
*/
556+
def until(end: Char): NumericRange.Exclusive[Char] = NumericRange(self, end, '\u0001')
557+
558+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to but not including `end`.
559+
*
560+
* @param end The final bound of the range to make.
561+
* @param step The number to increase by for each step of the range.
562+
*/
563+
def until(end: Char, step: Char): NumericRange.Exclusive[Char] = NumericRange(self, end, step)
564+
565+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to and including `end`.
566+
*
567+
* @param end The final bound of the range to make.
568+
*/
569+
def to(end: Char): NumericRange.Inclusive[Char] = NumericRange.inclusive(self, end, '\u0001')
570+
571+
/** A [[scala.collection.immutable.NumericRange]] from `this` up to and including `end`.
572+
*
573+
* @param end The final bound of the range to make.
574+
* @param step The number to increase by for each step of the range.
575+
*/
576+
def to(end: Char, step: Char): NumericRange.Inclusive[Char] = NumericRange.inclusive(self, end, step)
577+
578+
// ------------------------------------
579+
// For source compatibility with the previous API, when the rhs is an Int, we want an int Range
580+
581+
/** A [[scala.collection.immutable.Range]] from `this` up to but not including `end`.
582+
*
583+
* @param end The final bound of the range to make.
584+
*/
585+
def until(end: Int): Range = Range(self.toInt, end, 1)
586+
587+
/** A [[scala.collection.immutable.Range]] from `this` up to but not including `end`.
588+
*
589+
* @param end The final bound of the range to make.
590+
* @param step The number to increase by for each step of the range.
591+
*/
592+
def until(end: Int, step: Int): Range = Range(self.toInt, end, step)
593+
594+
/** A [[scala.collection.immutable.Range]] from `this` up to and including `end`.
595+
*
596+
* @param end The final bound of the range to make.
597+
*/
598+
def to(end: Int): Range.Inclusive = Range.inclusive(self.toInt, end, 1)
599+
600+
/** A [[scala.collection.immutable.Range]] from `this` up to and including `end`.
601+
*
602+
* @param end The final bound of the range to make.
603+
* @param step The number to increase by for each step of the range.
604+
*/
605+
def to(end: Int, step: Int): Range.Inclusive = Range.inclusive(self.toInt, end, step)
606+
607+
// ------------------------------------
608+
// Unicode properties
609+
610+
def asDigit: Int = Character.digit(self, Character.MAX_RADIX)
611+
612+
def isControl: Boolean = Character.isISOControl(self)
613+
def isDigit: Boolean = Character.isDigit(self)
614+
def isLetter: Boolean = Character.isLetter(self)
615+
def isLetterOrDigit: Boolean = Character.isLetterOrDigit(self)
616+
def isWhitespace: Boolean = Character.isWhitespace(self)
617+
def isSpaceChar: Boolean = Character.isSpaceChar(self)
618+
def isHighSurrogate: Boolean = Character.isHighSurrogate(self)
619+
def isLowSurrogate: Boolean = Character.isLowSurrogate(self)
620+
def isSurrogate: Boolean = isHighSurrogate || isLowSurrogate
621+
def isUnicodeIdentifierStart: Boolean = Character.isUnicodeIdentifierStart(self)
622+
def isUnicodeIdentifierPart: Boolean = Character.isUnicodeIdentifierPart(self)
623+
def isIdentifierIgnorable: Boolean = Character.isIdentifierIgnorable(self)
624+
def isMirrored: Boolean = Character.isMirrored(self)
625+
626+
def isLower: Boolean = Character.isLowerCase(self)
627+
def isUpper: Boolean = Character.isUpperCase(self)
628+
def isTitleCase: Boolean = Character.isTitleCase(self)
629+
630+
def toLower: Char = Character.toLowerCase(self)
631+
def toUpper: Char = Character.toUpperCase(self)
632+
def toTitleCase: Char = Character.toTitleCase(self)
633+
634+
def getType: Int = Character.getType(self)
635+
def getNumericValue: Int = Character.getNumericValue(self)
636+
def getDirectionality: Byte = Character.getDirectionality(self)
637+
def reverseBytes: Char = Character.reverseBytes(self)
638+
}
639+
}

0 commit comments

Comments
 (0)