Skip to content

wip: attempt at not using slow shapeless for (large) co-products #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ class Build(val context: cbt.Context) extends XdotaiFreeSoftwareBuild{
Resolver( mavenCentral ).bind(
"com.chuusai" %% "shapeless" % "2.3.1",
"org.cvogt" %% "scala-extensions" % "0.5.1"
) ++ Seq(
DirectoryDependency(projectDirectory ++ "/macros")
)
}
85 changes: 76 additions & 9 deletions diff.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.cvogt.scala.StringExtensions
import java.util.UUID
import org.cvogt.scala.debug.ThrowableExtensions
import org.cvogt.scala.constraint.{CaseClass, SingletonObject, boolean}
import scala.reflect.macros._

object `package` {
def red( s: String ) = Console.RED + s + Console.RESET
Expand Down Expand Up @@ -103,12 +104,14 @@ object DiffShowFields {

abstract class DiffShowInstancesLowPriority {
// enable for debugging if your type class can't be found
/*
implicit def otherDiffShow[T: scala.reflect.ClassTag]: DiffShow[T] = new DiffShow[T]{
private val T = scala.reflect.classTag[T].toString
// throw new Exception( s"Cannot find DiffShow[$T]" )
def show(v: T) = red(new Exception(s"ERROR: Cannot find DiffShow[$T] to show value " + v).showStackTrace)
def diff(l:T, r:T) = Error( new Exception(s"ERROR: Cannot find DiffShow[$T] to show values ($l, $r)").showStackTrace )
}
*/
}

abstract class DiffShowInstances extends DiffShowInstances2 {
Expand All @@ -128,11 +131,6 @@ abstract class DiffShowInstances extends DiffShowInstances2 {
def diff( l: Some[T], r: Some[T] ) = ds.diff(l.get, r.get).map( s => constructor("Some", List("" -> s)) )
}

implicit def singletonObjectDiffShow[T: SingletonObject]: DiffShow[T] = new DiffShow[T]{
def show( t: T ) = t.getClass.getSimpleName.stripSuffix("$")
def diff( l: T, r: T ) = Identical( l )
}

def primitive[T]( show: T => String ) = {
val _show = show
new DiffShow[T]{
Expand Down Expand Up @@ -180,7 +178,7 @@ abstract class DiffShowInstances extends DiffShowInstances2 {

implicit def caseClassDiffShow[T <: Product with Serializable: CaseClass, L <: HList](
implicit
ev: boolean.![SingletonObject[T]],
//ev: boolean.![SingletonObject[T]],
labelled: LabelledGeneric.Aux[T, L],
hlistShow: Lazy[DiffShowFields[L]]
): DiffShow[T] = new CaseClassDiffShow[T, L]
Expand Down Expand Up @@ -213,6 +211,11 @@ abstract class DiffShowInstances extends DiffShowInstances2 {
abstract class DiffShowInstances2 extends DiffShowInstancesLowPriority {
self: DiffShowInstances =>

implicit def singletonObjectDiffShow[T: SingletonObject]: DiffShow[T] = new DiffShow[T]{
def show( t: T ) = t.getClass.getSimpleName.stripSuffix("$")
def diff( l: T, r: T ) = Identical( l )
}

// helper methods
def constructor( name: String, keyValues: List[( String, String )] ): String = constructorOption( name, keyValues.map( Option( _ ) ) )
def constructorOption( name: String, keyValues: List[Option[( String, String )]] ): String = {
Expand Down Expand Up @@ -315,7 +318,7 @@ abstract class DiffShowInstances2 extends DiffShowInstancesLowPriority {
}

// instances for Shapeless types

/*
implicit object CNilDiffShow extends DiffShow[CNil] {
def show( t: CNil ) = throw new Exception("Methods in CNil type class instance should never be called. Right shapeless?")
def diff( left: CNil, right: CNil ) = throw new Exception("Methods in CNil type class instance should never be called. Right shapeless?")
Expand All @@ -341,14 +344,78 @@ abstract class DiffShowInstances2 extends DiffShowInstancesLowPriority {
}
}
}
*/

implicit def sealedDiffShow[T, L <: Coproduct](
/*
implicit def sealedDiffShow[T: Sealed, L <: Coproduct](
implicit
coproduct: Generic.Aux[T, L],
coproductShow: Lazy[DiffShow[L]]
): DiffShow[T] = new DiffShow[T] {
def show( t: T ) = coproductShow.value.show( coproduct.to( t ) )
def diff( l: T, r: T ) = coproductShow.value.diff( coproduct.to( l ), coproduct.to( r ) )
}
implicit val diffShow = this
*/

implicit def sealedDiffShow[T](implicit ev1:Abstract[T], ev2: Sealed[T]): DiffShow[T] = macro SealedDiffShow.sealedDiffShowMacro[T]
}

class SealedDiffShow(override val c: blackbox.Context) extends SealedDispatchMacros(c){
import c.universe._
def sealedDiffShowMacro[T:c.WeakTypeTag](ev1:Tree, ev2: Tree) = {
val T = c.weakTypeOf[T].typeSymbol
q"""
new _root_.ai.x.diff.DiffShow[$T] {
val dispatchMap = _root_.ai.x.diff.SealedDispatch.sealedDispatch[$T,_root_.ai.x.diff.DiffShow]
def dispatch(t: $T) = dispatchMap(t.getClass).asInstanceOf[_root_.ai.x.diff.DiffShow[$T]]
def show( t: $T ) = dispatch(t).show(t)
def diff( l: $T, r: $T ) = dispatch(l).diff(l,r)
}
"""
}
}

object SealedDispatch{
def sealedDispatch[T, TypeClass[_]](implicit ev1:Abstract[T], ev2: Sealed[T]): Map[Class[_],TypeClass[_]] = macro SealedDispatchMacros.sealedDispatch[T,TypeClass]
}
class SealedDispatchMacros(val c: blackbox.Context){
import c.universe._
def sealedDispatch[T: c.WeakTypeTag, TypeClass[_]](ev1: Tree, ev2: Tree)(implicit ev3: c.WeakTypeTag[TypeClass[_]]) = {
val T = c.weakTypeOf[T]
val TypeClass = c.weakTypeOf[TypeClass[_]].typeSymbol
val (classes, verifiers) = knownDirectSubclassesAndVerifier(T.dealias.typeSymbol.asClass)
val mappings = classes.map(cls => q"classOf[$cls] -> implicitly[$TypeClass[$cls]]")
q"""{
..$verifiers
Map[Class[_],$TypeClass[_]]( ..$mappings )
}"""
}
def knownDirectSubclassesAndVerifier( T: ClassSymbol ): ( Set[ClassSymbol], List[Tree] ) = {
val subs = T.knownDirectSubclasses

// hack to detect breakage of knownDirectSubclasses as suggested in
// https://gitter.im/scala/scala/archives/2015/05/05 and
// https://gist.github.com/retronym/639080041e3fecf58ba9
val global = c.universe.asInstanceOf[scala.tools.nsc.Global]
def checkSubsPostTyper = if ( subs != T.knownDirectSubclasses )
c.error(
c.macroApplication.pos,
s"""No child classes found for $T. If there clearly are child classes,
Try moving the call lower in the file, into a separate file, a sibbling package, a separate sbt sub project or else.
This is caused by https://issues.scala-lang.org/browse/SI-7046 and can only be avoided by manually moving the call.
It is triggered when a macro call happend in a place, where typechecking of $T hasn't been completed yet.
Completion is required in order to find subclasses.
"""
)

val checkSubsPostTyperTypTree =
new global.TypeTreeWithDeferredRefCheck()( () => { checkSubsPostTyper; global.TypeTree( global.NoType ) } ).asInstanceOf[TypTree]

val name = TypeName( c.freshName( "VerifyKnownDirectSubclassesPostTyper" ) )

(
subs.map( _.asClass ).toSet,
List( q"type ${name} = $checkSubsPostTyperTypTree" )
)
}
}
8 changes: 8 additions & 0 deletions macros/build/build.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import cbt._
// cbt:https://github.com/cvogt/cbt.git#b5d86995128a45c33117ecfb7365f0eb2b450a61
class Build(val context: cbt.Context) extends AdvancedScala{
override def dependencies = super.dependencies ++
Resolver( mavenCentral ).bind(
"org.scala-lang" % "scala-compiler" % "2.11.8"
)
}
36 changes: 36 additions & 0 deletions macros/macros.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ai.x.diff
import scala.reflect.macros._

final class Abstract[T]
object Abstract{
def checkMacro[T:c.WeakTypeTag](c: blackbox.Context): c.Expr[Abstract[T]] = {
import c.universe._
val T = c.weakTypeOf[T]
if(
!T.typeSymbol.isAbstract
) c.error(c.enclosingPosition,s"$T is not abstract")
c.Expr[Abstract[T]](q"new _root_.ai.x.diff.Abstract[$T]")
}
/**
fails compilation if T is not a singleton object class
meaning this can be used as an implicit to check
*/
implicit def check[T]: Abstract[T] = macro checkMacro[T]
}

final class Sealed[T]
object Sealed{
def checkMacro[T:c.WeakTypeTag](c: blackbox.Context): c.Expr[Sealed[T]] = {
import c.universe._
val T = c.weakTypeOf[T]
if(
!T.typeSymbol.isClass || !T.typeSymbol.asClass.isSealed
) c.error(c.enclosingPosition,s"$T is not sealed")
c.Expr[Sealed[T]](q"new _root_.ai.x.diff.Sealed[$T]")
}
/**
fails compilation if T is not a singleton object class
meaning this can be used as an implicit to check
*/
implicit def check[T]: Sealed[T] = macro checkMacro[T]
}
14 changes: 14 additions & 0 deletions test/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.util.UUID

sealed trait Parent
case class Bar( s: String, i: Int ) extends Parent
case class Fooo( bar: Bar, b: List[Int], parent: Option[Int] ) extends Parent
case class Foo( bar: Bar, b: List[Int], parent: Option[Parent] ) extends Parent

case class Id(int: Int)
Expand All @@ -17,6 +18,18 @@ object Main extends App {
val fooAsParent: Parent = foo
val fooString = """Foo( b = Set(), bar = Bar( i = 1, s = "test" ), parent = None )"""

//implicitly[org.cvogt.scala.constraint.boolean.![org.cvogt.scala.constraint.SingletonObject[Foo]]]

//println(implicitly[DiffShow[Bar]](DiffShow.caseClassDiffShow).show(bar))
//implicitly[Lazy[DiffShow[Parent]]]
implicitly[DiffShow[Parent]]
//println(implicitly[DiffShow[Fooo]](DiffShow.caseClassDiffShow))
//println(implicitly[shapeless.LabelledGeneric[Bar]])
//println(implicitly[DiffShowFields[Bar]])
//println(implicitly[shapeless.LabelledGeneric[Foo]])
//println(implicitly[DiffShowFields[Foo]])
//println(implicitly[DiffShow[Foo]](DiffShow.caseClassDiffShow).show(foo))
/*
def assertIdentical[T:DiffShow](left: T, right: T, expectedOutput: String = null) = {
val c = DiffShow.diff(left, right)
assert(c.isIdentical, c.toString)
Expand Down Expand Up @@ -204,4 +217,5 @@ object Main extends App {
)
"""
*/
*/
}
1 change: 1 addition & 0 deletions test/build/build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import cbt._
// cbt:https://github.com/cvogt/cbt.git#b5d86995128a45c33117ecfb7365f0eb2b450a61
class Build(val context: Context) extends ScalaParadise{
override def dependencies = super.dependencies :+ DirectoryDependency(projectDirectory.getParentFile)
//override def scalacOptions = super.scalacOptions ++ Seq( "-Xprint:typer")
}