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

Commit

Permalink
Merge pull request #424 from jvican/improve-implicit-search
Browse files Browse the repository at this point in the history
Improve implicit search drastically
  • Loading branch information
jvican committed Jun 1, 2016
2 parents d10ccee + c9a726e commit 4f5dd03
Show file tree
Hide file tree
Showing 35 changed files with 464 additions and 384 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ import scala.pickling.Defaults.{ pickleOps, unpickleOps }
// import scala.pickling.functions._

// Import picklers for specific types
import scala.pickling.Defaults.{ stringPickler, intPickler, refUnpickler, nullPickler }
import scala.pickling.Defaults.{ stringPickler, intPickler, refPicklerUnpickler, nullPickler }

case class Pumpkin(kind: String)
// Manually generate a pickler using macro
Expand Down
7 changes: 3 additions & 4 deletions benchmark/WikiGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,9 @@ object WikiGraphPicklingBench extends WikiGraphBenchmark {
// }
// }
// }
implicit lazy val picklerUnpicklerColonColonVertex: Pickler[::[Vertex]] with Unpickler[::[Vertex]] = implicitly
implicit lazy val picklerUnpicklerVectorVertex: Pickler[Vector[Vertex]] with Unpickler[Vector[Vertex]] = Defaults.vectorPickler[Vertex]
implicit val picklerGraph = implicitly[Pickler[Graph]]
implicit val unpicklerGraph = implicitly[Unpickler[Graph]]
implicit lazy val picklerUnpicklerColonColonVertex: AbstractPicklerUnpickler[::[Vertex]] = implicitly
implicit lazy val picklerUnpicklerVectorVertex: AbstractPicklerUnpickler[Vector[Vertex]] = Defaults.vectorPickler[Vertex]
implicit val picklerUnpicklerGraph = implicitly[AbstractPicklerUnpickler[Graph]]

override def run(): Unit = {
val pickle = data.pickle
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/scala/pickling/HasCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ package scala.pickling
// even though `c.universe` doesn't have `compat`
// in Scala 2.11.0, it will be ignored, becase `import c.universe._`
// brings its own `compat` in scope and that one takes precedence
private object HasCompat { val compat = ??? }
private[pickling] object HasCompat { val compat = ??? }
1 change: 0 additions & 1 deletion core/src/main/scala/scala/pickling/Pickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ object Unpickler {
abstract class AbstractPicklerUnpickler[T] extends Pickler[T] with Unpickler[T]
object PicklerUnpickler {
def apply[T](p: Pickler[T], u: Unpickler[T]): AbstractPicklerUnpickler[T] = new DelegatingPicklerUnpickler(p, u)
//def generate[T]: Pickler[T] with Unpickler[T] = macro Compat.PicklerUnpicklerMacros_impl[T]
def generate[T]: AbstractPicklerUnpickler[T] = macro generator.Compat.genPicklerUnpickler_impl[T]
/** This is a private implementation of PicklerUnpickler that delegates pickle and unpickle to underlying. */
private class DelegatingPicklerUnpickler[T](p: Pickler[T], u: Unpickler[T]) extends AbstractPicklerUnpickler[T] {
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/scala/scala/pickling/PicklingErrors.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package scala.pickling

import scala.pickling.generator.AlgorithmLogger

private[pickling] object Feedback {

def failedGenerationMsg(x: String, y: String, z: String) =
Expand All @@ -13,6 +15,20 @@ private[pickling] object Feedback {

}

private[pickling] object MacrosErrors {

import scala.reflect.macros.Context

def failedGeneration(culprit: String)(implicit c: Context, l: AlgorithmLogger): Nothing = {
l.error(s"Failed pickler/unpickler generation for $culprit")
???
}

def impossibleGeneration(culprit: String)(implicit c: Context, l: AlgorithmLogger) =
l.abort("Scala pickling can't generate pickling logic for $culprit")

}

object PicklingErrors {

import Feedback._
Expand Down
50 changes: 6 additions & 44 deletions core/src/main/scala/scala/pickling/Tools.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import scala.language.existentials
import scala.reflect.macros.Context
import scala.reflect.api.Universe

import scala.collection.mutable.{Map => MutableMap, ListBuffer => MutableList, WeakHashMap, Set => MutableSet}
import scala.collection.mutable.{Stack => MutableStack, Queue => MutableQueue}

import scala.collection.mutable.{Map => MutableMap, ListBuffer => MutableList, WeakHashMap}
import java.lang.ref.WeakReference

import HasCompat._
Expand All @@ -21,7 +19,7 @@ object Tools {
def unapply[T](optRef: Option[WeakReference[T]]): Option[T] =
if (optRef.nonEmpty) {
val result = optRef.get.get
if (result != null) Some(result) else None
Option(result)
} else None
}

Expand Down Expand Up @@ -180,7 +178,7 @@ class Tools[C <: Context](val c: C) {
if (baseSym.isFinal || baseSym.isModuleClass) Nil // FIXME: http://groups.google.com/group/scala-internals/browse_thread/thread/e2b786120b6d118d
else if (blackList(baseSym)) Nil
else {
var unsorted = {
val unsorted = {
if (baseSym.isClass && treatAsSealed(baseSym.asClass)) sealedHierarchyScan()
else sourcepathScan() // sourcepathAndClasspathScan()
}
Expand Down Expand Up @@ -218,6 +216,9 @@ trait RichTypes {
case _ => false
}

def isScalaOrJavaPrimitive: Boolean =
isEffectivelyPrimitive || tpe.typeSymbol == StringClass

def isEffectivelyFinal = tpe.typeSymbol.isEffectivelyFinal

def isNotNullable = tpe.typeSymbol.isNotNullable
Expand Down Expand Up @@ -353,45 +354,6 @@ abstract class Macro extends RichTypes { self =>
def syntheticPicklerUnpicklerQualifiedName(tpe: Type): TypeName = syntheticBaseQualifiedName(tpe) + syntheticPicklerUnpicklerSuffix()
def syntheticPicklerUnpicklerSuffix(): String = "PicklerUnpickler"

def preferringAlternativeImplicits(body: => Tree): Tree = {
import Compat._

val candidates = c.enclosingImplicits
if (candidates.isEmpty)
return body
val ourPt = candidates.head.pt

def debug(msg: Any) = {
val padding = " " * (candidates.length - 1)
// Console.err.println(padding + msg)
}

debug("can we enter " + ourPt + "?")
debug(candidates)

if ((candidates.size >= 2) && {
val theirPt = candidates.tail.head.pt
ourPt =:= theirPt
}) {
debug(s"no, because: ourPt = $ourPt, theirPt = ${candidates.tail.head.pt}")
// c.diverge()
c.abort(c.enclosingPosition, "stepping aside: repeating itself")
} else {
debug(s"not sure, need to explore alternatives")
c.inferImplicitValue(ourPt, silent = true) match {
case success if success != EmptyTree =>
debug(s"no, because there's $success")
c.abort(c.enclosingPosition, "stepping aside: there are other candidates")
// c.diverge()
case _ =>
debug("yes, there are no obstacles. entering " + ourPt)
val result = body
debug("result: " + result)
result
}
}
}

private var reflectivePrologueEmitted = false // TODO: come up with something better
def reflectively(target: String, fir: FieldIR)(body: Tree => Tree): List[Tree] = reflectively(newTermName(target), fir)(body)

Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/scala/pickling/generator/Compat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import scala.reflect.runtime.{universe => ru}

private[pickling] object Compat {

// Provides a source compatibility stub
implicit class HasPt[A, B](t: (A, B)) {
def pt: A = t._1
}

def genPicklerUnpickler_impl[T: c.WeakTypeTag](c: Context): c.Expr[AbstractPicklerUnpickler[T] with Generated] = {
val c0: c.type = c
val bundle = new { val c: c0.type = c0 } with PicklingMacros
Expand Down
81 changes: 0 additions & 81 deletions core/src/main/scala/scala/pickling/generator/Macros.scala

This file was deleted.

120 changes: 120 additions & 0 deletions core/src/main/scala/scala/pickling/generator/PicklingMacros.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package scala.pickling
package generator

import scala.collection.mutable

import HasCompat._

private[pickling] trait PicklingMacros
extends Macro with SourceGenerator with TypeAnalysis {

implicit val implContext = c

import c.universe._
import compat._

val symbols = new IrScalaSymbols[c.universe.type, c.type](c.universe, tools)
// TODO - also allow import to disable the warnings.
val disableWillRobinsonWarning =
java.lang.Boolean.getBoolean("pickling.willrobinson.disablewarning")
// TODO - We should have more customization than this
val handleCaseClassSubclasses = !configOption(
typeOf[IsIgnoreCaseClassSubclasses])
val generator =
if (isStaticOnly) {
PicklingAlgorithm.aggregate(
Seq(new CaseClassPickling(
allowReflection = false,
careAboutSubclasses = handleCaseClassSubclasses),
AdtPickling,
ScalaSingleton))
} else {
PicklingAlgorithm.aggregate(
Seq(new CaseClassPickling(
allowReflection = true,
careAboutSubclasses = handleCaseClassSubclasses),
AdtPickling,
ScalaSingleton,
new ExternalizablePickling,
new WillRobinsonPickling(
showWarnings = !disableWillRobinsonWarning)))
}

implicit object logger extends AlgorithmLogger {
def warn(msg: String): Unit = c.warning(c.enclosingPosition, msg)
def error(msg: String): Unit = c.error(c.enclosingPosition, msg)
def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg)
def debug(msg: String): Unit = {
// These are enabled when -verbose is enabled.
c.info(c.enclosingPosition, msg, force = false)
}
}

import MacrosErrors._

def checkClassType(tpe: c.Type, isUnpickle: Boolean = true): Unit = {
import definitions._
tpe.normalize match {
case tpe1 if tpe1.typeSymbol.isClass => () // This case is fine.
case NothingTpe => impossibleGeneration("Nothing")
case RefinedType(parents, decls) => impossibleGeneration("refined type")
case _ if tpe.isScalaOrJavaPrimitive =>
impossibleGeneration("primitive types")
case _ => impossibleGeneration(s"non-class type $tpe")
}
}

def preferExistingImplicits(body: => Tree): Tree = {

import Compat._
import Console._

val candidates = c.enclosingImplicits
if (candidates.isEmpty) return body
val ourPt = candidates.head.pt

def debug(msg: Any) = {
val padding = " " * (candidates.length - 1)
//Console.err.println(padding + msg)
}

debug(MAGENTA_B + "Can we enter " + ourPt + "?" + RESET)
debug(candidates)

if ((candidates.size >= 2) && {
/* This checks if `inferImplicitValue` has called the same implicit
* macro and avoids non-termination by circular invocation */
val theirPt = candidates.tail.head.pt
ourPt =:= theirPt
}) {
debug(RED_B + s"No, parent type is the same $ourPt" + RESET)
c.abort(c.enclosingPosition, "stepping aside: repeating itself")
} else {
debug(YELLOW_B + s"Not sure, need to explore alternatives" + RESET)
c.inferImplicitValue(ourPt, silent = true) match {
case success if success != EmptyTree =>
debug(BLUE_B + s"No, because there's $success" + RESET)
c.abort(c.enclosingPosition,
"stepping aside: there are other candidates")
case _ =>
debug(GREEN_B + s"Yes, there are no obstacles. Entering: $ourPt" +
RESET)
debug(s"Generated tree: $body")
body
}
}
}

def genPicklerUnpickler[T : c.WeakTypeTag]: c.Tree = {
preferExistingImplicits {
val tpe = computeType[T]
checkClassType(tpe)
val sym = symbols.newClass(tpe)
val impl = PicklingAlgorithm.run(generator)(sym, logger)
impl map generatePicklerUnpicklerClass[T] match {
case Some(tree) => tree
case None => MacrosErrors.failedGeneration(tpe.toString)
}
}
}
}
2 changes: 0 additions & 2 deletions core/src/main/scala/scala/pickling/generator/algorithms.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package scala.pickling
package generator

import scala.reflect.api.Universe

/** An interface so we can pass logging to these algorithms at runtime/during testing. */
private[pickling] trait AlgorithmLogger {
def warn(msg: String): Unit
Expand Down
Loading

0 comments on commit 4f5dd03

Please sign in to comment.