From 9fb5f1b809187a7c9b7bed7236b87ce3a8e5d8a2 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Wed, 3 Aug 2016 00:37:40 +0800 Subject: [PATCH] attempt at no using slow shapeless for (large) co-products --- build/build.scala | 2 + diff.scala | 85 +++++++++++++++++++++++++++++++++++----- macros/build/build.scala | 8 ++++ macros/macros.scala | 36 +++++++++++++++++ test/Main.scala | 14 +++++++ test/build/build.scala | 1 + 6 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 macros/build/build.scala create mode 100644 macros/macros.scala diff --git a/build/build.scala b/build/build.scala index 65f6c64..545a6ab 100644 --- a/build/build.scala +++ b/build/build.scala @@ -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") ) } diff --git a/diff.scala b/diff.scala index c76ba80..4129906 100644 --- a/diff.scala +++ b/diff.scala @@ -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 @@ -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 { @@ -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]{ @@ -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] @@ -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 = { @@ -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?") @@ -341,8 +344,10 @@ 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]] @@ -350,5 +355,67 @@ abstract class DiffShowInstances2 extends DiffShowInstancesLowPriority { 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" ) + ) + } } diff --git a/macros/build/build.scala b/macros/build/build.scala new file mode 100644 index 0000000..26f6cde --- /dev/null +++ b/macros/build/build.scala @@ -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" + ) +} diff --git a/macros/macros.scala b/macros/macros.scala new file mode 100644 index 0000000..0d2217d --- /dev/null +++ b/macros/macros.scala @@ -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] +} diff --git a/test/Main.scala b/test/Main.scala index 152fae3..bbf7ded 100644 --- a/test/Main.scala +++ b/test/Main.scala @@ -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) @@ -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) @@ -204,4 +217,5 @@ object Main extends App { ) """ */ + */ } diff --git a/test/build/build.scala b/test/build/build.scala index 9fac771..697f8dd 100644 --- a/test/build/build.scala +++ b/test/build/build.scala @@ -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") }