Skip to content

Commit 0f24d27

Browse files
committed
Deprecate the helpers in scala.deriving.
* `ArrayProduct` * `EmptyProduct` * `productElement` These helpers are not mandated by the spec, nor used by the compiler. They were used in a few tests for typeclass derivation, but it does not seem that they are fundamental. We now use replacements in the relevant tests. `EmptyProduct` can be replaced by `EmptyTuple`. `ArrayProduct` seems to be used by "unpickling" kinds of derivations (an impression confirmed by the community build). This is too specific a use case to be defined generally in `scala.deriving`. Moreover, it is not clear why Arrays receive special treatment, but not other kinds of sequences or even immutable arrays. `ArrayProduct` can be replaced by a custom `new Product {...}` wrapper, which in the case of our tests ends up being simpler than the "feature-rich" `ArrayProduct`. `productElement` seems to be more generally useful, but at the same time is very unsafe, and is not hard to re-implement. Removing it forces the call sites to perform the `asInstanceOf`s themselves, which is not a bad thing as it allows to better reason about the code. In particular, our tests benefitted from pushing the cast to `Product` ahead in the call chain, at a place where it is relatively easier to convince oneself that the value is indeed a `Product`.
1 parent e14c765 commit 0f24d27

File tree

5 files changed

+51
-43
lines changed

5 files changed

+51
-43
lines changed

library/src/scala/deriving/Helpers.scala

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala.deriving
22

33
/** Helper class to turn arrays into products */
4+
@deprecated("explicitly create a `new Product {...}` wrapper for the array or use `Tuple.fromArray`", "3.0.0-M2")
45
class ArrayProduct(val elems: Array[AnyRef]) extends Product {
56
def this(size: Int) = this(new Array[AnyRef](size))
67
def canEqual(that: Any): Boolean = true
@@ -11,8 +12,10 @@ class ArrayProduct(val elems: Array[AnyRef]) extends Product {
1112
}
1213

1314
/** The empty product */
15+
@deprecated("use EmptyTuple instead", "3.0.0-M2")
1416
object EmptyProduct extends ArrayProduct(Array.emptyObjectArray)
1517

1618
/** Helper method to select a product element */
19+
@deprecated("use x.asInstanceOf[Product].productElement(idx).asInstanceOf[T] instead", "3.0.0-M2")
1720
def productElement[T](x: Any, idx: Int): T =
1821
x.asInstanceOf[Product].productElement(idx).asInstanceOf[T]

tests/disabled/pos-macros/i7853/JsonEncoder_1.scala

+6-5
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ object JsonEncoder {
1212

1313
inline def encodeElem[T](elem: T): String = summonInline[JsonEncoder[T]].encode(elem)
1414

15-
inline def encodeElems[Elems <: Tuple](idx: Int)(value: Any): List[String] =
15+
inline def encodeElems[Elems <: Tuple](idx: Int)(value: Product): List[String] =
1616
inline erasedValue[Elems] match {
1717
case _: (elem *: elems1) =>
18-
encodeElem[elem](productElement[elem](value, idx)) :: encodeElems[elems1](idx + 1)(value)
18+
encodeElem[elem](value.productElement(idx).asInstanceOf[elem]) :: encodeElems[elems1](idx + 1)(value)
1919
case _ => Nil
2020
}
2121

@@ -25,8 +25,9 @@ object JsonEncoder {
2525
case m: Mirror.SumOf[T] =>
2626
"not supporting this case yet"
2727
case m: Mirror.ProductOf[T] =>
28-
val elems = encodeElems[m.MirroredElemTypes](0)(value)
29-
val labels = value.asInstanceOf[Product].productElementNames
28+
val valueProduct = value.asInstanceOf[Product]
29+
val elems = encodeElems[m.MirroredElemTypes](0)(valueProduct)
30+
val labels = valueProduct.productElementNames
3031
val keyValues = labels.zip(elems).map((k, v) => s"$k: $v")
3132
"{" + (keyValues).mkString(", ") + "}"
3233
case other =>
@@ -45,4 +46,4 @@ object JsonEncoder {
4546
given stringEncoder as JsonEncoder[String] {
4647
def encode(value: String) = value
4748
}
48-
}
49+
}

tests/pos-special/typeclass-scaling.scala

+19-17
Original file line numberDiff line numberDiff line change
@@ -220,24 +220,22 @@ object typeclasses {
220220

221221
inline def tryEql[TT](x: TT, y: TT): Boolean = summonInline[Eq[TT]].eql(x, y)
222222

223-
inline def eqlElems[Elems <: Tuple](n: Int)(x: Any, y: Any): Boolean =
223+
inline def eqlElems[Elems <: Tuple](n: Int)(x: Product, y: Product): Boolean =
224224
inline erasedValue[Elems] match {
225225
case _: (elem *: elems1) =>
226-
tryEql[elem](productElement[elem](x, n), productElement[elem](y, n)) &&
226+
tryEql[elem](x.productElement(n).asInstanceOf[elem], y.productElement(n).asInstanceOf[elem]) &&
227227
eqlElems[elems1](n + 1)(x, y)
228228
case _: EmptyTuple =>
229229
true
230230
}
231231

232-
inline def eqlProduct[T](m: Mirror.ProductOf[T])(x: Any, y: Any): Boolean =
233-
eqlElems[m.MirroredElemTypes](0)(x, y)
234-
235232
inline def eqlCases[Alts](n: Int)(x: Any, y: Any, ord: Int): Boolean =
236233
inline erasedValue[Alts] match {
237234
case _: (alt *: alts1) =>
238235
if (ord == n)
239236
summonFrom {
240-
case m: Mirror.ProductOf[`alt`] => eqlElems[m.MirroredElemTypes](0)(x, y)
237+
case m: Mirror.ProductOf[`alt`] =>
238+
eqlElems[m.MirroredElemTypes](0)(x.asInstanceOf[Product], y.asInstanceOf[Product])
241239
}
242240
else eqlCases[alts1](n + 1)(x, y, ord)
243241
case _: EmptyTuple =>
@@ -251,7 +249,7 @@ object typeclasses {
251249
val ord = m.ordinal(x)
252250
ord == m.ordinal(y) && eqlCases[m.MirroredElemTypes](0)(x, y, ord)
253251
case m: Mirror.ProductOf[T] =>
254-
eqlElems[m.MirroredElemTypes](0)(x, y)
252+
eqlElems[m.MirroredElemTypes](0)(x.asInstanceOf[Product], y.asInstanceOf[Product])
255253
}
256254
}
257255

@@ -275,10 +273,10 @@ object typeclasses {
275273

276274
inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonInline[Pickler[T]].pickle(buf, x)
277275

278-
inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any): Unit =
276+
inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Product): Unit =
279277
inline erasedValue[Elems] match {
280278
case _: (elem *: elems1) =>
281-
tryPickle[elem](buf, productElement[elem](x, n))
279+
tryPickle[elem](buf, x.productElement(n).asInstanceOf[elem])
282280
pickleElems[elems1](n + 1)(buf, x)
283281
case _: EmptyTuple =>
284282
}
@@ -288,30 +286,34 @@ object typeclasses {
288286
case _: (alt *: alts1) =>
289287
if (ord == n)
290288
summonFrom {
291-
case m: Mirror.ProductOf[`alt`] => pickleElems[m.MirroredElemTypes](0)(buf, x)
289+
case m: Mirror.ProductOf[`alt`] => pickleElems[m.MirroredElemTypes](0)(buf, x.asInstanceOf[Product])
292290
}
293291
else pickleCases[alts1](n + 1)(buf, x, ord)
294292
case _: EmptyTuple =>
295293
}
296294

297295
inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonInline[Pickler[T]].unpickle(buf)
298296

299-
inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: ArrayProduct): Unit =
297+
inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: Array[Any]): Unit =
300298
inline erasedValue[Elems] match {
301299
case _: (elem *: elems1) =>
302-
elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef]
300+
elems(n) = tryUnpickle[elem](buf)
303301
unpickleElems[elems1](n + 1)(buf, elems)
304302
case _: EmptyTuple =>
305303
}
306304

307305
inline def unpickleCase[T, Elems <: Tuple](buf: mutable.ListBuffer[Int], m: Mirror.ProductOf[T]): T = {
308306
inline val size = constValue[Tuple.Size[Elems]]
309307
inline if (size == 0)
310-
m.fromProduct(EmptyProduct)
308+
m.fromProduct(EmptyTuple)
311309
else {
312-
val elems = new ArrayProduct(size)
310+
val elems = new Array[Any](size)
313311
unpickleElems[Elems](0)(buf, elems)
314-
m.fromProduct(elems)
312+
m.fromProduct(new Product {
313+
def canEqual(that: Any): Boolean = true
314+
def productArity: Int = size
315+
def productElement(idx: Int): Any = elems(idx)
316+
})
315317
}
316318
}
317319

@@ -336,7 +338,7 @@ object typeclasses {
336338
buf += ord
337339
pickleCases[m.MirroredElemTypes](0)(buf, x, ord)
338340
case m: Mirror.ProductOf[T] =>
339-
pickleElems[m.MirroredElemTypes](0)(buf, x)
341+
pickleElems[m.MirroredElemTypes](0)(buf, x.asInstanceOf[Product])
340342
}
341343
def unpickle(buf: mutable.ListBuffer[Int]): T =
342344
inline ev match {
@@ -391,4 +393,4 @@ object Test extends App {
391393
implicitly[Pickler[E14[Int]]]
392394
implicitly[Pickler[E15[Int]]]
393395
implicitly[Pickler[E16[Int]]]
394-
}
396+
}

tests/run/deriving.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ sealed trait U
88
case class C() extends U
99

1010
object Test extends App {
11-
import deriving.{Mirror, EmptyProduct}
11+
import deriving._
1212

1313
case class AA[X >: Null <: AnyRef](x: X, y: X, z: String)
1414

@@ -21,7 +21,7 @@ object Test extends App {
2121
}
2222
summon[Mirror.Of[B.type]] match {
2323
case m: Mirror.Product =>
24-
println(m.fromProduct(EmptyProduct))
24+
println(m.fromProduct(EmptyTuple))
2525
}
2626
summon[Mirror.Of[T]] match {
2727
case m: Mirror.SumOf[T] =>

tests/run/typeclass-derivation3.scala

+21-19
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,22 @@ object typeclasses {
3939
case eq: Eq[TT] => eq.eql(x, y)
4040
}
4141

42-
inline def eqlElems[Elems <: Tuple](n: Int)(x: Any, y: Any): Boolean =
42+
inline def eqlElems[Elems <: Tuple](n: Int)(x: Product, y: Product): Boolean =
4343
inline erasedValue[Elems] match {
4444
case _: (elem *: elems1) =>
45-
tryEql[elem](productElement[elem](x, n), productElement[elem](y, n)) &&
45+
tryEql[elem](x.productElement(n).asInstanceOf[elem], y.productElement(n).asInstanceOf[elem]) &&
4646
eqlElems[elems1](n + 1)(x, y)
4747
case _: EmptyTuple =>
4848
true
4949
}
5050

51-
inline def eqlProduct[T](m: Mirror.ProductOf[T])(x: Any, y: Any): Boolean =
52-
eqlElems[m.MirroredElemTypes](0)(x, y)
53-
5451
inline def eqlCases[Alts](n: Int)(x: Any, y: Any, ord: Int): Boolean =
5552
inline erasedValue[Alts] match {
5653
case _: (alt *: alts1) =>
5754
if (ord == n)
5855
summonFrom {
59-
case m: Mirror.ProductOf[`alt`] => eqlElems[m.MirroredElemTypes](0)(x, y)
56+
case m: Mirror.ProductOf[`alt`] =>
57+
eqlElems[m.MirroredElemTypes](0)(x.asInstanceOf[Product], y.asInstanceOf[Product])
6058
}
6159
else eqlCases[alts1](n + 1)(x, y, ord)
6260
case _: EmptyTuple =>
@@ -70,7 +68,7 @@ object typeclasses {
7068
val ord = m.ordinal(x)
7169
ord == m.ordinal(y) && eqlCases[m.MirroredElemTypes](0)(x, y, ord)
7270
case m: Mirror.ProductOf[T] =>
73-
eqlElems[m.MirroredElemTypes](0)(x, y)
71+
eqlElems[m.MirroredElemTypes](0)(x.asInstanceOf[Product], y.asInstanceOf[Product])
7472
}
7573
}
7674

@@ -96,10 +94,10 @@ object typeclasses {
9694
case pkl: Pickler[T] => pkl.pickle(buf, x)
9795
}
9896

99-
inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any): Unit =
97+
inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Product): Unit =
10098
inline erasedValue[Elems] match {
10199
case _: (elem *: elems1) =>
102-
tryPickle[elem](buf, productElement[elem](x, n))
100+
tryPickle[elem](buf, x.productElement(n).asInstanceOf[elem])
103101
pickleElems[elems1](n + 1)(buf, x)
104102
case _: EmptyTuple =>
105103
}
@@ -109,7 +107,7 @@ object typeclasses {
109107
case _: (alt *: alts1) =>
110108
if (ord == n)
111109
summonFrom {
112-
case m: Mirror.ProductOf[`alt`] => pickleElems[m.MirroredElemTypes](0)(buf, x)
110+
case m: Mirror.ProductOf[`alt`] => pickleElems[m.MirroredElemTypes](0)(buf, x.asInstanceOf[Product])
113111
}
114112
else pickleCases[alts1](n + 1)(buf, x, ord)
115113
case _: EmptyTuple =>
@@ -119,22 +117,26 @@ object typeclasses {
119117
case pkl: Pickler[T] => pkl.unpickle(buf)
120118
}
121119

122-
inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: ArrayProduct): Unit =
120+
inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: Array[Any]): Unit =
123121
inline erasedValue[Elems] match {
124122
case _: (elem *: elems1) =>
125-
elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef]
123+
elems(n) = tryUnpickle[elem](buf)
126124
unpickleElems[elems1](n + 1)(buf, elems)
127125
case _: EmptyTuple =>
128126
}
129127

130128
inline def unpickleCase[T, Elems <: Tuple](buf: mutable.ListBuffer[Int], m: Mirror.ProductOf[T]): T = {
131129
inline val size = constValue[Tuple.Size[Elems]]
132130
inline if (size == 0)
133-
m.fromProduct(EmptyProduct)
131+
m.fromProduct(EmptyTuple)
134132
else {
135-
val elems = new ArrayProduct(size)
133+
val elems = new Array[Any](size)
136134
unpickleElems[Elems](0)(buf, elems)
137-
m.fromProduct(elems)
135+
m.fromProduct(new Product {
136+
def canEqual(that: Any): Boolean = true
137+
def productArity: Int = size
138+
def productElement(idx: Int): Any = elems(idx)
139+
})
138140
}
139141
}
140142

@@ -159,7 +161,7 @@ object typeclasses {
159161
buf += ord
160162
pickleCases[m.MirroredElemTypes](0)(buf, x, ord)
161163
case m: Mirror.ProductOf[T] =>
162-
pickleElems[m.MirroredElemTypes](0)(buf, x)
164+
pickleElems[m.MirroredElemTypes](0)(buf, x.asInstanceOf[Product])
163165
}
164166
def unpickle(buf: mutable.ListBuffer[Int]): T =
165167
inline ev match {
@@ -188,13 +190,13 @@ object typeclasses {
188190

189191
inline def tryShow[T](x: T): String = summonInline[Show[T]].show(x)
190192

191-
inline def showElems[Elems <: Tuple, Labels <: Tuple](n: Int)(x: Any): List[String] =
193+
inline def showElems[Elems <: Tuple, Labels <: Tuple](n: Int)(x: Product): List[String] =
192194
inline erasedValue[Elems] match {
193195
case _: (elem *: elems1) =>
194196
inline erasedValue[Labels] match {
195197
case _: (label *: labels1) =>
196198
val formal = constValue[label]
197-
val actual = tryShow(productElement[elem](x, n))
199+
val actual = tryShow(x.productElement(n).asInstanceOf[elem])
198200
s"$formal = $actual" :: showElems[elems1, labels1](n + 1)(x)
199201
}
200202
case _: EmptyTuple =>
@@ -205,7 +207,7 @@ object typeclasses {
205207
val label = constValue[m.MirroredLabel]
206208
inline m match {
207209
case m: Mirror.Singleton => label
208-
case _ => showElems[m.MirroredElemTypes, m.MirroredElemLabels](0)(x).mkString(s"$label(", ", ", ")")
210+
case _ => showElems[m.MirroredElemTypes, m.MirroredElemLabels](0)(x.asInstanceOf[Product]).mkString(s"$label(", ", ", ")")
209211
}
210212
}
211213

0 commit comments

Comments
 (0)