Skip to content
This repository was archived by the owner on Feb 20, 2019. It is now read-only.

Commit d63e92e

Browse files
committed
Merge pull request #162 from scala/support-for-transient
Adds ability to pickle/unpickle things marked @transient
2 parents 6e92d67 + ecbe614 commit d63e92e

File tree

6 files changed

+132
-6
lines changed

6 files changed

+132
-6
lines changed

core/src/main/scala/pickling/Compat.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ object Compat {
8585
c.Expr[FastTypeTag[T]](bundle.impl[T])
8686
}
8787

88+
def FastTypeTagMacros_implClassTag[T: c.WeakTypeTag](c: Context): c.Expr[FastTypeTag[T]] = {
89+
val c0: c.type = c
90+
val bundle = new { val c: c0.type = c0 } with FastTypeTagMacros
91+
c.Expr[FastTypeTag[T]](bundle.implClassTag[T])
92+
}
93+
8894
def FastTypeTagMacros_apply(c: Context)(key: c.Expr[String]): c.Expr[FastTypeTag[t]] forSome { type t } = {
8995
val c0: c.type = c
9096
val bundle = new { val c: c0.type = c0 } with FastTypeTagMacros

core/src/main/scala/pickling/FastTags.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import scala.reflect.macros.Context
55
import scala.reflect.api.{Universe => ApiUniverse}
66
import scala.reflect.runtime.{universe => ru}
77
import language.experimental.macros
8+
import scala.reflect.ClassTag
89

910
trait FastTypeTag[T] extends Equals {
1011
def mirror: ru.Mirror
@@ -19,6 +20,8 @@ trait FastTypeTag[T] extends Equals {
1920
object FastTypeTag {
2021
implicit def materializeFastTypeTag[T]: FastTypeTag[T] = macro Compat.FastTypeTagMacros_impl[T]
2122

23+
implicit def materializeFastTypeTagOfClassTag[T]: FastTypeTag[ClassTag[T]] = macro Compat.FastTypeTagMacros_implClassTag[T]
24+
2225
private def stdTag[T: ru.TypeTag]: FastTypeTag[T] = apply(scala.reflect.runtime.currentMirror, ru.typeOf[T], ru.typeOf[T].key).asInstanceOf[FastTypeTag[T]]
2326
implicit val Null = stdTag[Null]
2427
implicit val Byte = stdTag[Byte]
@@ -108,6 +111,17 @@ trait FastTypeTagMacros extends Macro {
108111
}
109112
"""
110113
}
114+
def implClassTag[T: c.WeakTypeTag]: c.Tree = {
115+
import c.universe._
116+
val T = weakTypeOf[T]
117+
q"""
118+
new FastTypeTag[ClassTag[$T]] {
119+
def mirror = scala.pickling.internal.`package`.currentMirror
120+
lazy val tpe = scala.reflect.runtime.universe.weakTypeOf[ClassTag[$T]]
121+
def key = "ClassTag[" + ${T.key} + "]"
122+
}
123+
"""
124+
}
111125
def apply(key: c.Tree): c.Tree = {
112126
import c.universe._
113127
q"""scala.pickling.FastTypeTag(scala.pickling.internal.`package`.currentMirror, $key)"""

core/src/main/scala/pickling/Macros.scala

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,26 @@ trait UnpicklerMacros extends Macro {
258258

259259
val someConstructorIsPrivate = ctors.exists(_.isPrivate)
260260
// println(s"someConstructorIsPrivate: $someConstructorIsPrivate")
261-
val canCallCtor = !someConstructorIsPrivate && !cir.fields.exists(_.isErasedParam) && isPreciseType
262-
// println(s"canCallCtor: $canCallCtor")
261+
val canCallCtor = !someConstructorIsPrivate && !cir.fields.exists(_.isErasedParam) && isPreciseType && {
262+
// there must not be a transient ctor param
263+
// STEP 1: we need to figure out if there is a transient ctor param
264+
val primaryCtor = tpe.declaration(nme.CONSTRUCTOR) match {
265+
case overloaded: TermSymbol => overloaded.alternatives.head.asMethod // NOTE: primary ctor is always the first in the list
266+
case primaryCtor: MethodSymbol => primaryCtor
267+
case NoSymbol => NoSymbol
268+
}
269+
270+
val allAccessors = tpe.declarations.collect { case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }
271+
272+
val (filteredAccessors, transientAccessors) = allAccessors.partition(irs.notMarkedTransient) // shoulsai iaobjsobj aaaobjsecta insteeyd m d daybe
273+
274+
val hasTransientParam = (primaryCtor != NoSymbol) && primaryCtor.asMethod.paramss.flatten.exists { sym =>
275+
transientAccessors.exists(acc => acc.name.toString == sym.name.toString)
276+
}
277+
278+
!hasTransientParam
279+
}
280+
// STEP 2: remove transient fields from the "pending fields", the fields that need to be restored.
263281

264282
// TODO: for ultimate loop safety, pendingFields should be hoisted to the outermost unpickling scope
265283
// For example, in the snippet below, when unpickling C, we'll need to move the b.c assignment not

core/src/main/scala/pickling/ir/IRs.scala

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class IRs[U <: Universe with Singleton](val uni: U) {
2424
// this part is interesting to unpicklers
2525
def hasSetter = setter.isDefined
2626
def isErasedParam = isParam && accessor.isEmpty // TODO: this should somehow communicate with the constructors phase!
27-
def isReifiedParam = isParam && accessor.nonEmpty
2827
def isNonParam = !isParam
2928
}
3029
case class ClassIR(tpe: Type, parent: ClassIR, fields: List[FieldIR]) extends PickleIR
@@ -33,14 +32,33 @@ class IRs[U <: Universe with Singleton](val uni: U) {
3332
private type C = ClassIR
3433

3534
// TODO: minimal versus verbose PickleFormat. i.e. someone might want all concrete inherited fields in their pickle
35+
36+
def notMarkedTransient(sym: TermSymbol): Boolean = {
37+
val tr = scala.util.Try {
38+
(sym.accessed == NoSymbol) || // if there is no backing field, then it cannot be marked transient
39+
!sym.accessed.annotations.exists(_.tpe =:= typeOf[scala.transient])
40+
}
41+
tr.isFailure || tr.get
42+
}
43+
44+
/** Creates FieldIRs for the given type, tp.
45+
*/
3646
private def fields(tp: Type): Q = {
3747
val ctor = tp.declaration(nme.CONSTRUCTOR) match {
3848
case overloaded: TermSymbol => overloaded.alternatives.head.asMethod // NOTE: primary ctor is always the first in the list
3949
case primaryCtor: MethodSymbol => primaryCtor
4050
case NoSymbol => NoSymbol
4151
}
42-
val ctorParams = if (ctor != NoSymbol) ctor.asMethod.paramss.flatten.map(_.asTerm) else Nil
43-
val allAccessors = tp.declarations.collect{ case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }
52+
53+
val allAccessors = tp.declarations.collect { case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }
54+
55+
val (filteredAccessors, transientAccessors) = allAccessors.partition(notMarkedTransient)
56+
57+
val ctorParams = if (ctor != NoSymbol) ctor.asMethod.paramss.flatten.flatMap { sym =>
58+
if (transientAccessors.exists(acc => acc.name.toString == sym.name.toString)) List()
59+
else List(sym.asTerm)
60+
} else Nil
61+
4462
val (paramAccessors, otherAccessors) = allAccessors.partition(_.isParamAccessor)
4563

4664
def mkFieldIR(sym: TermSymbol, param: Option[TermSymbol], accessor: Option[MethodSymbol]) = {
@@ -62,7 +80,7 @@ class IRs[U <: Universe with Singleton](val uni: U) {
6280

6381
private val f1 = (q1: Q, q2: Q) => q1 ++ q2
6482

65-
private val f2 = (c1: C, c2: C) => ClassIR(c2.tpe, c1, fields(c2.tpe))
83+
private val f2 = (c1: C, c2: C) => ClassIR(c2.tpe, c1, c2.fields)
6684

6785
private val f3 = (c: C) =>
6886
c.tpe.baseClasses
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package scala.pickling.implicitparams
2+
3+
import org.scalatest.FunSuite
4+
import scala.pickling._
5+
import json._
6+
7+
8+
case class Person(implicit name: String, age: Int)
9+
object Test {
10+
class SimpleImplParamTest extends FunSuite {
11+
test("main") {
12+
implicit val nme = "Harry"
13+
implicit val age = 18
14+
15+
val per = Person()
16+
val p = per.pickle
17+
val up = p.unpickle[Person]
18+
assert(per == up)
19+
}
20+
}
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package scala.pickling.transienttest
2+
3+
import org.scalatest.FunSuite
4+
import scala.reflect.ClassTag
5+
import scala.pickling._
6+
import json._
7+
8+
case class Person(val name: String , @transient val ssNumber: Int) {
9+
override def toString = s"Person($name)"
10+
}
11+
12+
class Dependency[T]
13+
14+
class SparkConf(loadDefaults: Boolean)
15+
class SparkContext(config: SparkConf)
16+
17+
class RDD[T: ClassTag](
18+
@transient private var sc: SparkContext,
19+
@transient private var deps: Seq[Dependency[_]]
20+
)
21+
22+
class RangePartitioner[K : ClassTag, V](
23+
@transient val partitions: Int,
24+
@transient val rdd: RDD[_ <: Product2[K,V]],
25+
private var ascending: Boolean = true) {
26+
override def toString = s"RangePartitioner(ascending = $ascending)"
27+
}
28+
29+
class TransientSimpleTest extends FunSuite {
30+
test("main") {
31+
val per = Person("Jenny", 123)
32+
val p: JSONPickle = per.pickle
33+
val up = p.unpickle[Person]
34+
assert(up.ssNumber == 0)
35+
assert(per.toString == up.toString)
36+
}
37+
}
38+
39+
class TransientSparkTest extends FunSuite {
40+
test("main") {
41+
val sc = new SparkContext(new SparkConf(true))
42+
val rdd = new RDD[(Int, Int)](sc, Seq(new Dependency()))
43+
val rp = new RangePartitioner[Int, Int](2, rdd)
44+
val p: JSONPickle = rp.pickle
45+
val up = p.unpickle[RangePartitioner[Int, Int]]
46+
assert(rp.toString == up.toString)
47+
}
48+
}
49+

0 commit comments

Comments
 (0)