Skip to content

Commit 035f6e2

Browse files
authored
Merge pull request #499 from RustedBones/tc-serialization-test-scal3
[scala3] Add test for serializable generated type-class
2 parents 1bcf4d7 + b231259 commit 035f6e2

File tree

4 files changed

+87
-11
lines changed

4 files changed

+87
-11
lines changed

core/src/main/scala/magnolia1/impl.scala

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import scala.reflect.*
66

77
import Macro.*
88

9+
// scala3 lambda generated during derivation reference outer scope
10+
// This fails the typeclass serialization if the outer scope is not serializable
11+
// workaround with this with a serializable fuction
12+
private trait SerializableFunction0[+R] extends Function0[R] with Serializable:
13+
def apply(): R
14+
private trait SerializableFunction1[-T1, +R] extends Function1[T1, R] with Serializable:
15+
def apply(v1: T1): R
16+
917
object CaseClassDerivation:
1018
inline def fromMirror[Typeclass[_], A](
1119
product: Mirror.ProductOf[A]
@@ -97,12 +105,17 @@ object CaseClassDerivation:
97105
case _: (EmptyTuple, EmptyTuple) =>
98106
Nil
99107
case _: ((l *: ltail), (p *: ptail)) =>
100-
def unsafeCast(any: Any) = Option.when(any == null || (any: @unchecked).isInstanceOf[p])(any.asInstanceOf[p])
101108
val label = constValue[l].asInstanceOf[String]
109+
val tc = new SerializableFunction0[Typeclass[p]]:
110+
override def apply(): Typeclass[p] = summonInline[Typeclass[p]]
111+
112+
val d = new SerializableFunction0[Option[p]]:
113+
private def unsafeCast(any: Any) = Option.when(any == null || (any: @unchecked).isInstanceOf[p])(any.asInstanceOf[p])
114+
override def apply(): Option[p] = defaults.get(label).flatten.flatMap(d => unsafeCast(d.apply))
102115
paramFromMaps[Typeclass, A, p](
103116
label,
104-
CallByNeed(summonInline[Typeclass[p]]),
105-
CallByNeed.withValueEvaluator(defaults.get(label).flatten.flatMap(d => unsafeCast(d.apply))),
117+
CallByNeed.createLazy(tc),
118+
CallByNeed.createValueEvaluator(d),
106119
repeated,
107120
annotations,
108121
inheritedAnnotations,
@@ -172,7 +185,16 @@ trait SealedTraitDerivation:
172185
mm.asInstanceOf[m.type],
173186
0
174187
)
175-
case _ =>
188+
case _ => {
189+
val tc = new SerializableFunction0[Typeclass[s]]:
190+
override def apply(): Typeclass[s] = summonFrom {
191+
case tc: Typeclass[`s`] => tc
192+
case _ => deriveSubtype(summonInline[Mirror.Of[s]])
193+
}
194+
val isType = new SerializableFunction1[A, Boolean]:
195+
override def apply(a: A): Boolean = a.isInstanceOf[s & A]
196+
val asType = new SerializableFunction1[A, s & A]:
197+
override def apply(a: A): s & A = a.asInstanceOf[s & A]
176198
List(
177199
new SealedTrait.Subtype[Typeclass, A, s](
178200
typeInfo[s],
@@ -181,14 +203,12 @@ trait SealedTraitDerivation:
181203
IArray.from(paramTypeAnns[A]),
182204
isObject[s],
183205
idx,
184-
CallByNeed(summonFrom {
185-
case tc: Typeclass[`s`] => tc
186-
case _ => deriveSubtype(summonInline[Mirror.Of[s]])
187-
}),
188-
x => x.isInstanceOf[s & A],
189-
_.asInstanceOf[s & A]
206+
CallByNeed.createLazy(tc),
207+
isType,
208+
asType
190209
)
191210
)
211+
}
192212
}
193213
(sub ::: subtypesFromMirror[A, tail](m, idx + 1)).distinctBy(_.typeInfo).sortBy(_.typeInfo.full)
194214
end SealedTraitDerivation

core/src/main/scala/magnolia1/interface.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,25 @@ object CallByNeed:
363363
/** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
364364
* happen once.
365365
*/
366+
def createLazy[A](a: () => A): CallByNeed[A] = new CallByNeed(a, () => false)
367+
368+
/** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
369+
* happen once.
370+
*
371+
* If by-name parameter causes serialization issue, use [[createLazy]].
372+
*/
366373
def apply[A](a: => A): CallByNeed[A] = new CallByNeed(() => a, () => false)
367374

368375
/** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
369376
* happen once. Evaluation of a value via `.valueEvaluator.map(evaluator => evaluator())` will happen every time the evaluator is called
370377
*/
378+
def createValueEvaluator[A](a: () => A): CallByNeed[A] = new CallByNeed(a, () => true)
379+
380+
/** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
381+
* happen once. Evaluation of a value via `.valueEvaluator.map(evaluator => evaluator())` will happen every time the evaluator is called
382+
*
383+
* If by-name parameter causes serialization issue, use [[withValueEvaluator]].
384+
*/
371385
def withValueEvaluator[A](a: => A): CallByNeed[A] = new CallByNeed(() => a, () => true)
372386
end CallByNeed
373387

examples/src/main/scala/magnolia1/examples/show.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import magnolia1._
66
*
77
* Note that this is a more general form of `Show` than is usual, as it permits the return type to be something other than a string.
88
*/
9-
trait Show[Out, T] { def show(value: T): Out }
9+
trait Show[Out, T] extends Serializable { def show(value: T): Out }
1010

1111
trait GenericShow[Out] extends AutoDerivation[[X] =>> Show[Out, X]] {
1212

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package magnolia1.tests
2+
3+
import magnolia1.*
4+
import magnolia1.examples.*
5+
6+
import java.io.*
7+
class SerializationTests extends munit.FunSuite:
8+
import SerializationTests.*
9+
10+
private def serializeToByteArray(value: Serializable): Array[Byte] =
11+
val buffer = new ByteArrayOutputStream()
12+
val oos = new ObjectOutputStream(buffer)
13+
oos.writeObject(value)
14+
buffer.toByteArray
15+
16+
private def deserializeFromByteArray(encodedValue: Array[Byte]): AnyRef =
17+
val ois = new ObjectInputStream(new ByteArrayInputStream(encodedValue))
18+
ois.readObject()
19+
20+
def ensureSerializable[T <: Serializable](value: T): T =
21+
deserializeFromByteArray(serializeToByteArray(value)).asInstanceOf[T]
22+
23+
test("generate serializable type-classes") {
24+
ensureSerializable(new Outer().showAddress)
25+
ensureSerializable(new Outer().showColor)
26+
}
27+
28+
object SerializationTests:
29+
sealed trait Entity
30+
case class Company(name: String) extends Entity
31+
case class Person(name: String, age: Int) extends Entity
32+
case class Address(line1: String, occupant: Person)
33+
34+
sealed trait Color
35+
case object Red extends Color
36+
case object Green extends Color
37+
case object Blue extends Color
38+
case object Orange extends Color
39+
case object Pink extends Color
40+
class Outer:
41+
val showAddress: Show[String, Address] = summon[Show[String, Address]]
42+
val showColor: Show[String, Color] = summon[Show[String, Color]]

0 commit comments

Comments
 (0)