From 8857f91ed4a6b8c5723cd7b3a4bf20d9278b8199 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 12:25:06 +0200 Subject: [PATCH 01/11] Deprecate old FractionalOps methods in favor of extension methods --- library/src/scala/math/Fractional.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/library/src/scala/math/Fractional.scala b/library/src/scala/math/Fractional.scala index e0469d9cb054..5ec3e5070481 100644 --- a/library/src/scala/math/Fractional.scala +++ b/library/src/scala/math/Fractional.scala @@ -19,10 +19,14 @@ import scala.language.implicitConversions trait Fractional[T] extends Numeric[T] { def div(x: T, y: T): T + extension (lhs: T) def /(rhs: T): T = div(lhs, rhs) + + @deprecated("use the extension methods available instead", since = "3.10.0") class FractionalOps(lhs: T) extends NumericOps(lhs) { def /(rhs: T) = div(lhs, rhs) } - override implicit def mkNumericOps(lhs: T): FractionalOps = + @deprecated("use the extension methods available instead", since = "3.10.0") + override def mkNumericOps(lhs: T): FractionalOps = new FractionalOps(lhs) } @@ -30,7 +34,8 @@ object Fractional { @inline def apply[T](implicit frac: Fractional[T]): Fractional[T] = frac trait ExtraImplicits { - implicit def infixFractionalOps[T](x: T)(implicit num: Fractional[T]): Fractional[T]#FractionalOps = new num.FractionalOps(x) + @deprecated("use the extension methods available instead", since = "3.10.0") + def infixFractionalOps[T](x: T)(implicit num: Fractional[T]): Fractional[T]#FractionalOps = new num.FractionalOps(x) } object Implicits extends ExtraImplicits } From 2541024c96e6769771f2658feeaa239e1fefac7e Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 12:25:17 +0200 Subject: [PATCH 02/11] Deprecate old IntegralOps methods in favor of extension methods --- library/src/scala/math/Integral.scala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/src/scala/math/Integral.scala b/library/src/scala/math/Integral.scala index b06936b28045..2489d4d19b19 100644 --- a/library/src/scala/math/Integral.scala +++ b/library/src/scala/math/Integral.scala @@ -20,12 +20,22 @@ trait Integral[T] extends Numeric[T] { def quot(x: T, y: T): T def rem(x: T, y: T): T + extension (lhs: T) { + def /(rhs: T): T = quot(lhs, rhs) + + def %(rhs: T): T = rem(lhs, rhs) + + def /%(rhs: T): (T, T) = (quot(lhs, rhs), rem(lhs, rhs)) + } + + @deprecated("use the extension methods available instead", since = "3.10.0") class IntegralOps(lhs: T) extends NumericOps(lhs) { def /(rhs: T) = quot(lhs, rhs) def %(rhs: T) = rem(lhs, rhs) def /%(rhs: T) = (quot(lhs, rhs), rem(lhs, rhs)) } - override implicit def mkNumericOps(lhs: T): IntegralOps = new IntegralOps(lhs) + @deprecated("use the extension methods available instead", since = "3.10.0") + override def mkNumericOps(lhs: T): IntegralOps = new IntegralOps(lhs) } object Integral { @@ -41,7 +51,8 @@ object Integral { * @param num the implicit `Integral` instance for type `T` * @return an `IntegralOps` instance providing integral operators on `x` */ - implicit def infixIntegralOps[T](x: T)(implicit num: Integral[T]): Integral[T]#IntegralOps = new num.IntegralOps(x) + @deprecated("use the extension methods available instead", since = "3.10.0") + def infixIntegralOps[T](x: T)(implicit num: Integral[T]): Integral[T]#IntegralOps = new num.IntegralOps(x) } object Implicits extends ExtraImplicits } From fc0ef289325dabe5c4ebe365bc1aa6fd2fffa883 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 12:25:30 +0200 Subject: [PATCH 03/11] Deprecate old NumericOps methods in favor of extension methods --- library/src/scala/math/Numeric.scala | 127 +++++++++++++++++---------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/library/src/scala/math/Numeric.scala b/library/src/scala/math/Numeric.scala index 9463a88189c0..c5f52dba25ce 100644 --- a/library/src/scala/math/Numeric.scala +++ b/library/src/scala/math/Numeric.scala @@ -36,7 +36,8 @@ object Numeric { * * @return a `NumericOps` wrapper around `x` that exposes infix arithmetic operators and numeric conversion methods */ - implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#NumericOps = new num.NumericOps(x) + @deprecated("use the extension methods available instead", since = "3.10.0") + def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#NumericOps = new num.NumericOps(x) } object Implicits extends ExtraImplicits { } @@ -49,10 +50,15 @@ object Numeric { def negate(x: BigInt): BigInt = -x def fromInt(x: Int): BigInt = BigInt(x) def parseString(str: String): Option[BigInt] = Try(BigInt(str)).toOption - def toInt(x: BigInt): Int = x.intValue - def toLong(x: BigInt): Long = x.longValue - def toFloat(x: BigInt): Float = x.floatValue - def toDouble(x: BigInt): Double = x.doubleValue + extension (x: BigInt) { + override def toInt: Int = x.intValue + + override def toLong: Long = x.longValue + + override def toFloat: Float = x.floatValue + + override def toDouble: Double = x.doubleValue + } } implicit object BigIntIsIntegral extends BigIntIsIntegral with Ordering.BigIntOrdering @@ -65,10 +71,12 @@ object Numeric { def negate(x: Int): Int = -x def fromInt(x: Int): Int = x def parseString(str: String): Option[Int] = StringParsers.parseInt(str) - def toInt(x: Int): Int = x - def toLong(x: Int): Long = x.toLong - def toFloat(x: Int): Float = x.toFloat - def toDouble(x: Int): Double = x.toDouble + extension (x: Int) { + override def toInt: Int = x + override def toLong: Long = x.toLong + override def toFloat: Float = x.toFloat + override def toDouble: Double = x.toDouble + } override def signum(x: Int): Int = math.signum(x) override def sign(x: Int): Int = math.signum(x) } @@ -83,10 +91,12 @@ object Numeric { def negate(x: Short): Short = (-x).toShort def fromInt(x: Int): Short = x.toShort def parseString(str: String): Option[Short] = StringParsers.parseShort(str) - def toInt(x: Short): Int = x.toInt - def toLong(x: Short): Long = x.toLong - def toFloat(x: Short): Float = x.toFloat - def toDouble(x: Short): Double = x.toDouble + extension (x: Short) { + override def toInt: Int = x.toInt + override def toLong: Long = x.toLong + override def toFloat: Float = x.toFloat + override def toDouble: Double = x.toDouble + } override def signum(x: Short): Int = math.signum(x.toInt) override def sign(x: Short): Short = math.signum(x.toInt).toShort } @@ -101,10 +111,12 @@ object Numeric { def negate(x: Byte): Byte = (-x).toByte def fromInt(x: Int): Byte = x.toByte def parseString(str: String): Option[Byte] = StringParsers.parseByte(str) - def toInt(x: Byte): Int = x.toInt - def toLong(x: Byte): Long = x.toLong - def toFloat(x: Byte): Float = x.toFloat - def toDouble(x: Byte): Double = x.toDouble + extension (x: Byte) { + override def toInt: Int = x.toInt + override def toLong: Long = x.toLong + override def toFloat: Float = x.toFloat + override def toDouble: Double = x.toDouble + } override def signum(x: Byte): Int = math.signum(x.toInt) override def sign(x: Byte): Byte = math.signum(x.toInt).toByte } @@ -119,10 +131,12 @@ object Numeric { def negate(x: Char): Char = (-x).toChar def fromInt(x: Int): Char = x.toChar def parseString(str: String): Option[Char] = Try(str.toInt.toChar).toOption - def toInt(x: Char): Int = x.toInt - def toLong(x: Char): Long = x.toLong - def toFloat(x: Char): Float = x.toFloat - def toDouble(x: Char): Double = x.toDouble + extension (x: Char) { + override def toInt: Int = x.toInt + override def toLong: Long = x.toLong + override def toFloat: Float = x.toFloat + override def toDouble: Double = x.toDouble + } override def signum(x: Char): Int = math.signum(x.toInt) override def sign(x: Char): Char = math.signum(x.toInt).toChar } @@ -137,10 +151,12 @@ object Numeric { def negate(x: Long): Long = -x def fromInt(x: Int): Long = x.toLong def parseString(str: String): Option[Long] = StringParsers.parseLong(str) - def toInt(x: Long): Int = x.toInt - def toLong(x: Long): Long = x - def toFloat(x: Long): Float = x.toFloat - def toDouble(x: Long): Double = x.toDouble + extension (x: Long) { + override def toInt: Int = x.toInt + override def toLong: Long = x + override def toFloat: Float = x.toFloat + override def toDouble: Double = x.toDouble + } override def signum(x: Long): Int = math.signum(x).toInt override def sign(x: Long): Long = math.signum(x) } @@ -153,13 +169,15 @@ object Numeric { def negate(x: Float): Float = -x def fromInt(x: Int): Float = x.toFloat def parseString(str: String): Option[Float] = StringParsers.parseFloat(str) - def toInt(x: Float): Int = x.toInt - def toLong(x: Float): Long = x.toLong - def toFloat(x: Float): Float = x - def toDouble(x: Float): Double = x.toDouble def div(x: Float, y: Float): Float = x / y - // logic in Numeric base trait mishandles abs(-0.0f) - override def abs(x: Float): Float = math.abs(x) + extension (x: Float) { + override def toInt: Int = x.toInt + override def toLong: Long = x.toLong + override def toFloat: Float = x + override def toDouble: Double = x.toDouble + // logic in Numeric base trait mishandles abs(-0.0f) + override def abs: Float = math.abs(x) + } // logic in Numeric base trait mishandles sign(-0.0f) and sign(Float.NaN) override def sign(x: Float): Float = math.signum(x) } @@ -172,13 +190,15 @@ object Numeric { def negate(x: Double): Double = -x def fromInt(x: Int): Double = x.toDouble def parseString(str: String): Option[Double] = StringParsers.parseDouble(str) - def toInt(x: Double): Int = x.toInt - def toLong(x: Double): Long = x.toLong - def toFloat(x: Double): Float = x.toFloat - def toDouble(x: Double): Double = x def div(x: Double, y: Double): Double = x / y - // logic in Numeric base trait mishandles abs(-0.0) - override def abs(x: Double): Double = math.abs(x) + extension (x: Double) { + override def toInt: Int = x.toInt + override def toLong: Long = x.toLong + override def toFloat: Float = x.toFloat + override def toDouble: Double = x + // logic in Numeric base trait mishandles abs(-0.0) + override def abs: Double = math.abs(x) + } // logic in Numeric base trait mishandles sign(-0.0) and sign(Double.NaN) override def sign(x: Double): Double = math.signum(x) } @@ -202,10 +222,12 @@ object Numeric { def negate(x: BigDecimal): BigDecimal = -x def fromInt(x: Int): BigDecimal = BigDecimal(x) def parseString(str: String): Option[BigDecimal] = Try(BigDecimal(str)).toOption - def toInt(x: BigDecimal): Int = x.intValue - def toLong(x: BigDecimal): Long = x.longValue - def toFloat(x: BigDecimal): Float = x.floatValue - def toDouble(x: BigDecimal): Double = x.doubleValue + extension (x: BigDecimal) { + override def toInt: Int = x.intValue + override def toLong: Long = x.longValue + override def toFloat: Float = x.floatValue + override def toDouble: Double = x.doubleValue + } } private object BigDecimalIsConflicted { private val _0 = BigDecimal(0) // cached zero is ordinarily cached for default math context @@ -233,15 +255,10 @@ trait Numeric[T] extends Ordering[T] { def negate(x: T): T def fromInt(x: Int): T def parseString(str: String): Option[T] - def toInt(x: T): Int - def toLong(x: T): Long - def toFloat(x: T): Float - def toDouble(x: T): Double def zero = fromInt(0) def one = fromInt(1) - def abs(x: T): T = if (lt(x, zero)) negate(x) else x @deprecated("use `sign` method instead", since = "2.13.0") def signum(x: T): Int = if (lt(x, zero)) -1 @@ -252,6 +269,19 @@ trait Numeric[T] extends Ordering[T] { else if (gt(x, zero)) one else zero + extension (lhs: T) { + def +(rhs: T): T = plus(lhs, rhs) + def -(rhs: T): T = minus(lhs, rhs) + def *(rhs: T): T = times(lhs, rhs) + def unary_- : T = negate(lhs) + def abs: T = if (lt(lhs, zero)) negate(lhs) else lhs + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + } + + @deprecated("use the extension methods available instead", since = "3.10.0") class NumericOps(lhs: T) { def +(rhs: T) = plus(lhs, rhs) def -(rhs: T) = minus(lhs, rhs) @@ -265,5 +295,6 @@ trait Numeric[T] extends Ordering[T] { def toFloat: Float = Numeric.this.toFloat(lhs) def toDouble: Double = Numeric.this.toDouble(lhs) } - implicit def mkNumericOps(lhs: T): NumericOps = new NumericOps(lhs) -} + @deprecated("use the extension methods available instead", since = "3.10.0") + def mkNumericOps(lhs: T): NumericOps = new NumericOps(lhs) +} \ No newline at end of file From b5abf95205d0e96518f5c8a1b031a0a65a17a9fb Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 12:25:40 +0200 Subject: [PATCH 04/11] Deprecate old OrderingOps methods in favor of extension methods --- library/src/scala/math/Ordering.scala | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/library/src/scala/math/Ordering.scala b/library/src/scala/math/Ordering.scala index fef5dc742261..5a329dd9685b 100644 --- a/library/src/scala/math/Ordering.scala +++ b/library/src/scala/math/Ordering.scala @@ -15,9 +15,8 @@ package math import scala.language.`2.13` import java.util.Comparator - import scala.language.implicitConversions -import scala.annotation.migration +import scala.annotation.{migration, targetName} import scala.annotation.unchecked.uncheckedOverride /** Ordering is a trait whose instances each represent a strategy for sorting @@ -247,6 +246,19 @@ trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializabl if (res1 != 0) res1 else ord.compare(f(x), f(y)) } + extension (lhs: T) { + def <(rhs: T): Boolean = lt(lhs, rhs) + def <=(rhs: T): Boolean = lteq(lhs, rhs) + def >(rhs: T): Boolean = gt(lhs, rhs) + def >=(rhs: T): Boolean = gteq(lhs, rhs) + @targetName("equivInfix") + def equiv(rhs: T): Boolean = outer.equiv(lhs, rhs) + @targetName("maxInfix") + def max(rhs: T): T = outer.max(lhs, rhs) + @targetName("minInfix") + def min(rhs: T): T = outer.min(lhs, rhs) + } + /** This inner class defines comparison operators available for `T`. * * It can't extend `AnyVal` because it is not a top-level class @@ -254,6 +266,7 @@ trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializabl * * @param lhs the left-hand side value for infix comparison operations */ + @deprecated("use the extension methods available instead", since = "3.10.0") class OrderingOps(lhs: T) { def <(rhs: T): Boolean = lt(lhs, rhs) def <=(rhs: T): Boolean = lteq(lhs, rhs) @@ -270,7 +283,8 @@ trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializabl * @param lhs the value to enrich with ordering operators * @return an `OrderingOps` wrapping `lhs` and providing infix comparison operators */ - implicit def mkOrderingOps(lhs: T): OrderingOps = new OrderingOps(lhs) + @deprecated("use the extension methods available instead", since = "3.10.0") + def mkOrderingOps(lhs: T): OrderingOps = new OrderingOps(lhs) } trait LowPriorityOrderingImplicits { @@ -395,7 +409,8 @@ object Ordering extends LowPriorityOrderingImplicits { * @param ord the implicit `Ordering` instance for type `T` * @return an `OrderingOps` instance providing infix comparison operators */ - implicit def infixOrderingOps[T](x: T)(implicit ord: Ordering[T]): Ordering[T]#OrderingOps = new ord.OrderingOps(x) + @deprecated("use the extension methods available instead", since = "3.10.0") + def infixOrderingOps[T](x: T)(implicit ord: Ordering[T]): Ordering[T]#OrderingOps = new ord.OrderingOps(x) } /** An object containing implicits which are not in the default scope. */ From 0ed8dbd113008d3d10a75c9c61648300dfc6bf90 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 12:25:52 +0200 Subject: [PATCH 05/11] fix leftovers --- .../collection/immutable/NumericRange.scala | 2 +- .../immutable/LazyListLazinessTest.scala | 10 ++++++---- .../immutable/NumericRangeTest.scala | 18 +++++++++-------- .../immutable/RangeConsistencyTest.scala | 20 +++++++++++-------- .../collection/immutable/RangeProps.scala | 10 ++++++---- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/library/src/scala/collection/immutable/NumericRange.scala b/library/src/scala/collection/immutable/NumericRange.scala index 28f10e981a04..d6849752f3f1 100644 --- a/library/src/scala/collection/immutable/NumericRange.scala +++ b/library/src/scala/collection/immutable/NumericRange.scala @@ -556,7 +556,7 @@ object NumericRange { @SerialVersionUID(3L) private final class NumericRangeIterator[T](self: NumericRange[T], num: Integral[T]) extends AbstractIterator[T] with Serializable { - import num.mkNumericOps + import num.* private var _hasNext = !self.isEmpty private var _next: T = self.start diff --git a/library/test/scala/collection/immutable/LazyListLazinessTest.scala b/library/test/scala/collection/immutable/LazyListLazinessTest.scala index 3f2e87b0a6b2..f8a7839e8420 100644 --- a/library/test/scala/collection/immutable/LazyListLazinessTest.scala +++ b/library/test/scala/collection/immutable/LazyListLazinessTest.scala @@ -895,10 +895,12 @@ class LazyListLazinessTest { override def negate(x: CustomLong) = I.negate(x.value) override def fromInt(x: Int) = I.fromInt(x) override def parseString(str: String) = I.parseString(str).map(CustomLong.apply) - override def toInt(x: CustomLong) = I.toInt(x.value) - override def toLong(x: CustomLong) = I.toLong(x.value) - override def toFloat(x: CustomLong) = I.toFloat(x.value) - override def toDouble(x: CustomLong) = I.toDouble(x.value) + extension (x: CustomLong) { + override def toInt: Int = I.toInt(x.value) + override def toLong: Long = I.toLong(x.value) + override def toFloat: Float = I.toFloat(x.value) + override def toDouble: Double = I.toDouble(x.value) + } override def compare(x: CustomLong, y: CustomLong) = I.compare(x.value, y.value) } } diff --git a/library/test/scala/collection/immutable/NumericRangeTest.scala b/library/test/scala/collection/immutable/NumericRangeTest.scala index ec832389594f..8d9ca1a62732 100644 --- a/library/test/scala/collection/immutable/NumericRangeTest.scala +++ b/library/test/scala/collection/immutable/NumericRangeTest.scala @@ -435,17 +435,19 @@ object NumericRangeTest { override def parseString(str: String): Option[NumericWrapper[T]] = tNum.parseString(str).map(NumericWrapper.apply) - override def toInt(x: NumericWrapper[T]): Int = - tNum.toInt(x.value) + extension (x: NumericWrapper[T]) { + override def toInt: Int = + tNum.toInt(x.value) - override def toLong(x: NumericWrapper[T]): Long = - tNum.toLong(x.value) + override def toLong: Long = + tNum.toLong(x.value) - override def toFloat(x: NumericWrapper[T]): Float = - tNum.toFloat(x.value) + override def toFloat: Float = + tNum.toFloat(x.value) - override def toDouble(x: NumericWrapper[T]): Double = - tNum.toDouble(x.value) + override def toDouble: Double = + tNum.toDouble(x.value) + } override def compare(x: NumericWrapper[T], y: NumericWrapper[T]): Int = tNum.compare(x.value, y.value) } diff --git a/library/test/scala/collection/immutable/RangeConsistencyTest.scala b/library/test/scala/collection/immutable/RangeConsistencyTest.scala index a367308fe6eb..584a2237e9cb 100644 --- a/library/test/scala/collection/immutable/RangeConsistencyTest.scala +++ b/library/test/scala/collection/immutable/RangeConsistencyTest.scala @@ -144,10 +144,12 @@ class RangeConsistencyTest { def negate(x: Int): Int = -x def plus(x: Int, y: Int): Int = x + y def times(x: Int, y: Int): Int = x*y - def toDouble(x: Int): Double = x.toDouble - def toFloat(x: Int): Float = x.toFloat - def toInt(x: Int): Int = x - def toLong(x: Int): Long = x.toLong + extension (x: Int) { + override def toDouble: Double = x.toDouble + override def toFloat: Float = x.toFloat + override def toInt: Int = x + override def toLong: Long = x.toLong + } def compare(x: Int, y: Int) = x compare y } val r = (Int.MinValue to Int.MaxValue by (1<<23)) @@ -185,10 +187,12 @@ class RangeConsistencyTest { def times(x: A, y: A): A = A(x.v * y.v) def quot(x: A, y: A): A = A(x.v / y.v) def rem(x: A, y: A): A = A(x.v % y.v) - def toDouble(x: A): Double = x.v.toDouble - def toFloat(x: A): Float = x.v.toFloat - def toInt(x: A): Int = x.v - def toLong(x: A): Long = x.v.toLong + extension (x: A) { + override def toDouble: Double = x.v.toDouble + override def toFloat: Float = x.v.toFloat + override def toInt: Int = x.v + override def toLong: Long = x.v.toLong + } } val r = NumericRange(A(1), A(10), A(1)) diff --git a/library/test/scala/collection/immutable/RangeProps.scala b/library/test/scala/collection/immutable/RangeProps.scala index eb443f65b8e8..56e60e9bef5f 100644 --- a/library/test/scala/collection/immutable/RangeProps.scala +++ b/library/test/scala/collection/immutable/RangeProps.scala @@ -155,10 +155,12 @@ abstract class RangeProps(kind: String) extends Properties("Range "+kind) { def minus(x: Int, y: Int): Int = ??? def negate(x: Int): Int = ??? def times(x: Int, y: Int): Int = ??? - def toDouble(x: Int): Double = ??? - def toFloat(x: Int): Float = ??? - def toInt(x: Int): Int = ((x % mod) + mod * 2) % mod - def toLong(x: Int): Long = ??? + extension (x: Int) { + override def toDouble: Double = ??? + override def toFloat: Float = ??? + override def toInt: Int = ((x % mod) + mod * 2) % mod + override def toLong: Long = ??? + } def compare(x: Int, y: Int): Int = ??? } From 17ef116ad87cd6a902c81165f8ff96dcdfeda251 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 14:33:02 +0200 Subject: [PATCH 06/11] Fix scala-library-sjs build: mkNumericOps is no longer implicit library-js has its own copy of NumericRange.scala that overrides the one in library/src for Scala.js builds. It still relied on `import num.mkNumericOps` as an implicit conversion to get `-`/`/` on T, which broke once mkNumericOps was deprecated and un-implicited. Mirrors the same fix already applied to library/src/.../NumericRange.scala. --- library-js/src/scala/collection/immutable/NumericRange.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library-js/src/scala/collection/immutable/NumericRange.scala b/library-js/src/scala/collection/immutable/NumericRange.scala index 8286a062d084..1fb13dc03319 100644 --- a/library-js/src/scala/collection/immutable/NumericRange.scala +++ b/library-js/src/scala/collection/immutable/NumericRange.scala @@ -497,7 +497,7 @@ object NumericRange { @SerialVersionUID(3L) private final class NumericRangeIterator[T](self: NumericRange[T], num: Integral[T]) extends AbstractIterator[T] with Serializable { - import num.mkNumericOps + import num.* private[this] var _hasNext = !self.isEmpty private[this] var _next: T = self.start From c158b008628b27b7f5878948d51b33c824fd6eb8 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 15:05:53 +0200 Subject: [PATCH 07/11] Exclude scala-js RangesTest.scala from sjs-junit test suite It implements Numeric[A] overriding toInt/toLong/toFloat/toDouble as regular methods, which no longer compile now that these are abstract extension methods on scala.math.Numeric. This is upstream Scala.js test-suite code fetched at build time, so it can't be fixed in place; excluded following the existing pattern for other Scala 3 incompatibilities in this list. --- project/Build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/Build.scala b/project/Build.scala index 2b635d5d0692..6f4c7b189c23 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2274,6 +2274,7 @@ object Build { -- "UTF16Test.scala" // refutable pattern match -- "CharsetTest.scala" // bogus @tailrec that Scala 2 ignores but Scala 3 flags as an error -- "ClassDiffersOnlyInCaseTest.scala" // looks like the Scala 3 compiler itself does not deal with that + -- "RangesTest.scala" // overrides Numeric#toInt/toLong/toFloat/toDouble as regular methods, which no longer override the extension methods in scala.math.Numeric )).get ++ (dir / "shared/src/test/require-sam" ** "*.scala").get From 5bae0bf44c27865e3cab532d62577e22f40ff9ab Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 16:19:28 +0200 Subject: [PATCH 08/11] Fix pos-macros/power-macro-3 test: stop relying on deprecated infixNumericOps The macro spliced \$x * \$x relying on the implicit conversion from Numeric.Implicits.infixNumericOps to provide \`*\` on the generic Num type parameter. That no longer resolves now that Numeric's toInt/toLong/ toFloat/toDouble/abs are extension methods. Call num.times(...) directly instead, which is unambiguous and was always the semantic behind \`*\`. --- tests/pos-macros/power-macro-3/Macro_1.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/pos-macros/power-macro-3/Macro_1.scala b/tests/pos-macros/power-macro-3/Macro_1.scala index d8273c65faf8..08075e4da462 100644 --- a/tests/pos-macros/power-macro-3/Macro_1.scala +++ b/tests/pos-macros/power-macro-3/Macro_1.scala @@ -1,8 +1,6 @@ import scala.quoted.* -import math.Numeric.Implicits.infixNumericOps - inline def power[Num](x: Num, inline n: Int)(using num: Numeric[Num]) = ${powerCode('x, 'n)(using 'num)} private def powerCode[Num: Type](x: Expr[Num], n: Expr[Int])(using Expr[Numeric[Num]])(using Quotes): Expr[Num] = @@ -12,13 +10,13 @@ private def powerCode[Num: Type](x: Expr[Num], n: Int)(using num: Expr[Numeric[N if (n == 0) '{ $num.one } else if (n % 2 == 0) '{ withGiven($num) { - val y = $x * $x + val y = $num.times($x, $x) ${ powerCode('y, n / 2) } } } else '{ withGiven($num) { - $x * ${powerCode(x, n - 1)} + $num.times($x, ${powerCode(x, n - 1)}) } } From 62a791e998d03ad9ff458238a60f06dcb5e1b6af Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 16:46:34 +0200 Subject: [PATCH 09/11] Restore * operator in pos-macros/power-macro-3 via an actual given withGiven's context-function-introduced instance wasn't picked up by extension method resolution for Numeric's * (which was the actual cause of the earlier "value * is not a member of Num" failure, not the deprecated infixNumericOps per se). Introducing the Numeric[Num] instance with a real `given` inside the quote fixes resolution, so the macro can go back to using * instead of calling num.times explicitly, and withGiven can be dropped entirely. --- tests/pos-macros/power-macro-3/Macro_1.scala | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/pos-macros/power-macro-3/Macro_1.scala b/tests/pos-macros/power-macro-3/Macro_1.scala index 08075e4da462..8f8e4ff06e77 100644 --- a/tests/pos-macros/power-macro-3/Macro_1.scala +++ b/tests/pos-macros/power-macro-3/Macro_1.scala @@ -9,15 +9,11 @@ private def powerCode[Num: Type](x: Expr[Num], n: Expr[Int])(using Expr[Numeric[ private def powerCode[Num: Type](x: Expr[Num], n: Int)(using num: Expr[Numeric[Num]])(using Quotes): Expr[Num] = if (n == 0) '{ $num.one } else if (n % 2 == 0) '{ - withGiven($num) { - val y = $num.times($x, $x) - ${ powerCode('y, n / 2) } - } + given Numeric[Num] = $num + val y = $x * $x + ${ powerCode('y, n / 2) } } else '{ - withGiven($num) { - $num.times($x, ${powerCode(x, n - 1)}) - } + given Numeric[Num] = $num + $x * ${powerCode(x, n - 1)} } - -inline def withGiven[U, T](inline x: T)(inline body: T ?=> U): U = body(using x) From 50661263979be5458dc23f79649af61c7711ee98 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Tue, 28 Jul 2026 16:53:39 +0200 Subject: [PATCH 10/11] Restore withGiven combinator in pos-macros/power-macro-3, fixed via x.type The original withGiven[U, T](inline x: T)(inline body: T ?=> U) widened the given to its nominal type T, which extension method resolution for * didn't pick up. Using x.type instead of a generic T ties the context function's given to the precise singleton type of the argument, which resolves correctly. inline had to be dropped from the x parameter since x.type requires a stable (non-inline) path. --- tests/pos-macros/power-macro-3/Macro_1.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/pos-macros/power-macro-3/Macro_1.scala b/tests/pos-macros/power-macro-3/Macro_1.scala index 8f8e4ff06e77..1f3b59752d8a 100644 --- a/tests/pos-macros/power-macro-3/Macro_1.scala +++ b/tests/pos-macros/power-macro-3/Macro_1.scala @@ -9,11 +9,15 @@ private def powerCode[Num: Type](x: Expr[Num], n: Expr[Int])(using Expr[Numeric[ private def powerCode[Num: Type](x: Expr[Num], n: Int)(using num: Expr[Numeric[Num]])(using Quotes): Expr[Num] = if (n == 0) '{ $num.one } else if (n % 2 == 0) '{ - given Numeric[Num] = $num - val y = $x * $x - ${ powerCode('y, n / 2) } + withGiven($num) { + val y = $x * $x + ${ powerCode('y, n / 2) } + } } else '{ - given Numeric[Num] = $num - $x * ${powerCode(x, n - 1)} + withGiven($num) { + $x * ${powerCode(x, n - 1)} + } } + +inline def withGiven[U](x: Any)(inline body: x.type ?=> U): U = body(using x) From ddf4fbb80684a5ca7dfa5205b5a1364348f29bb4 Mon Sep 17 00:00:00 2001 From: halotukozak Date: Thu, 13 Aug 2026 10:59:45 +0200 Subject: [PATCH 11/11] Update tests to match the new extension-based Numeric/Ordering ops The *Ops conversions (mkOrderingOps, mkNumericOps, infixOrderingOps, etc.) stay deprecated-but-non-implicit, so code needs to reach the new extension methods directly instead of falling back to the old implicit conversions. Updates checkfiles and test imports accordingly: - if-parse.scala: import the Ordering givens directly since the deprecated infixOrderingOps is no longer implicit. - more-specific.scala: accept the new [E008] extension-method diagnostic in place of the old [E007] implicit-conversion-rejection message; the restriction it guards (conversions must be more specific than AnyRef) still holds, just surfaced through a different code path. - transparent-inline-i12754-message.check: drop the now-invalid infixOrderingOps import suggestion. - i17371.scala: import the specific extension methods (ordA.>, ordB.<) instead of the no-op `import x.given` on a plain value, and drop the redundant `turns`/`circular` given-imports (they're already in scope via the using clause). - missing-implicit.scala: -/+/* on a Numeric-bounded T no longer need an explicit import, so only the > comparison still errors. --- tests/neg/missing-implicit.scala | 2 +- tests/neg/more-specific.check | 25 +++++++++++++------ .../transparent-inline-i12754-message.check | 5 ++-- tests/pos/if-parse.scala | 2 +- tests/warn/i17371.scala | 6 ++--- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/neg/missing-implicit.scala b/tests/neg/missing-implicit.scala index 3968eef97b22..11eecc81a921 100644 --- a/tests/neg/missing-implicit.scala +++ b/tests/neg/missing-implicit.scala @@ -2,5 +2,5 @@ import Predef.{byte2Byte as _, *} import math.Numeric def consume[T: Numeric](xs: List[T], limit: T): List[T] = xs match - case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error + case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error case _ => xs diff --git a/tests/neg/more-specific.check b/tests/neg/more-specific.check index 44db9a31ec28..4bdb1d353830 100644 --- a/tests/neg/more-specific.check +++ b/tests/neg/more-specific.check @@ -1,9 +1,20 @@ --- [E007] Type Mismatch Error: tests/neg/more-specific.scala:9:17 ------------------------------------------------------ +-- [E008] Not Found Error: tests/neg/more-specific.scala:9:14 ---------------------------------------------------------- 9 | if (x eq y) // error - | ^ - | Found: (y : T) - | Required: Object - | Note that implicit conversions were not tried because the result of an implicit conversion - | must be more specific than Object + | ^^^^ + | value eq is not a member of T. + | An extension method was tried, but could not be fully constructed: | - | longer explanation available when compiling with `-explain` + | eq(x) + | + | failed with: + | + | Found: (x : T) + | Required: AnyRef | Null + | Note that implicit conversions were not tried because the result of an implicit conversion + | must be more specific than AnyRef | Null + | + | The following import might fix the problem: + | + | import scala.reflect.Selectable.reflectiveSelectable + | + | diff --git a/tests/neg/transparent-inline-i12754-message.check b/tests/neg/transparent-inline-i12754-message.check index 2a4824eda85d..fbd93b50de41 100644 --- a/tests/neg/transparent-inline-i12754-message.check +++ b/tests/neg/transparent-inline-i12754-message.check @@ -3,10 +3,9 @@ | ^^^^^^^^^^^^^^ | value < is not a member of Any, but could be made available as an extension method. | - | One of the following imports might make progress towards fixing the problem: + | The following import might fix the problem: | - | import scala.math.Ordered.orderingToOrdered - | import scala.math.Ordering.Implicits.infixOrderingOps + | import scala.math.Ordering.Unit.< | | | diff --git a/tests/pos/if-parse.scala b/tests/pos/if-parse.scala index 9a99f6e216f5..b9474c600523 100644 --- a/tests/pos/if-parse.scala +++ b/tests/pos/if-parse.scala @@ -1,4 +1,4 @@ -import scala.math.Ordering.Implicits.infixOrderingOps +import scala.math.Ordering.given def test = if (1, 2) < (3, 4) then 1 else 2 diff --git a/tests/warn/i17371.scala b/tests/warn/i17371.scala index f9be76bfed07..3bb139a314b3 100644 --- a/tests/warn/i17371.scala +++ b/tests/warn/i17371.scala @@ -9,10 +9,10 @@ def Test() = val a: A = ??? val b: B = ??? - import ordA.given + import ordA.> val _ = a > a - import ordB.given + import ordB.< val _ = b < b // unminimized OP @@ -21,10 +21,8 @@ trait Turns[C: Circular, T] extends Ordering[T]: // warn Circular is not a marke extension (turns: T) def extract: C def f[K, T](start: T, end: T)(using circular: Circular[K], turns: Turns[K, T]): Boolean = - import turns.given if start > end then throw new IllegalArgumentException("start must be <= end") - import circular.given start.extract < end.extract // -Wunused:implicits warns for unused implicit evidence unless it is an empty interface (only universal members).