From d31a5c9549e84bff58f5ecd7a1e6ec2e70865174 Mon Sep 17 00:00:00 2001 From: Cary Robbins Date: Fri, 20 Apr 2018 18:24:11 -0500 Subject: [PATCH] refs #10: Added AsArray type class --- .../io/estatico/newtype/arrays/package.scala | 39 ++++++++++++++++ .../newtype/macros/NewTypeMacros.scala | 46 ++++++++++++++++++- .../newtype/macros/NewTypeMacrosTest.scala | 13 ++---- 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 shared/src/main/scala/io/estatico/newtype/arrays/package.scala diff --git a/shared/src/main/scala/io/estatico/newtype/arrays/package.scala b/shared/src/main/scala/io/estatico/newtype/arrays/package.scala new file mode 100644 index 0000000..b3eb163 --- /dev/null +++ b/shared/src/main/scala/io/estatico/newtype/arrays/package.scala @@ -0,0 +1,39 @@ +package io.estatico.newtype + +import scala.reflect.ClassTag + +package object arrays { + + trait AsArray[N] { + type Repr + def clsTag: ClassTag[Repr] + + final def empty: Array[N] = Array.empty(clsTag).asInstanceOf[Array[N]] + + final def apply(xs: N*): Array[N] = + Array(xs.asInstanceOf[Seq[Repr]]: _*)(clsTag).asInstanceOf[Array[N]] + + final def upcast[R](array: Array[R]): Array[N] = array.asInstanceOf[Array[N]] + + final def downcast(array: Array[N]): Array[Repr] = array.asInstanceOf[Array[Repr]] + } + + object AsArray { + + type Aux[N, R] = AsArray[N] { type Repr = R } + + def unsafeDerive[N, R](implicit ct: ClassTag[R]): Aux[N, R] = + new AsArray[N] { + type Repr = R + override def clsTag: ClassTag[Repr] = ct + } + + def empty[N](implicit ev: AsArray[N]): Array[N] = ev.empty + + def apply[N](xs: N*)(implicit ev: AsArray[N]): Array[N] = ev(xs: _*) + + def upcast[R, N](array: Array[R])(implicit ev: Aux[N, R]): Array[N] = ev.upcast(array) + + def downcast[N](array: Array[N])(implicit ev: AsArray[N]): Array[ev.Repr] = ev.downcast(array) + } +} diff --git a/shared/src/main/scala/io/estatico/newtype/macros/NewTypeMacros.scala b/shared/src/main/scala/io/estatico/newtype/macros/NewTypeMacros.scala index f7719ff..aee8dcc 100644 --- a/shared/src/main/scala/io/estatico/newtype/macros/NewTypeMacros.scala +++ b/shared/src/main/scala/io/estatico/newtype/macros/NewTypeMacros.scala @@ -1,6 +1,7 @@ package io.estatico.newtype.macros import io.estatico.newtype.Coercible +import io.estatico.newtype.arrays.AsArray import scala.reflect.ClassTag import scala.reflect.macros.blackbox @@ -39,8 +40,8 @@ private[macros] class NewTypeMacros(val c: blackbox.Context) val CoercibleCls = typeOf[Coercible[Nothing, Nothing]].typeSymbol val CoercibleObj = CoercibleCls.companion + val AsArrayObj = q"io.estatico.newtype.arrays.AsArray" val ClassTagCls = typeOf[ClassTag[Nothing]].typeSymbol - val ClassTagObj = ClassTagCls.companion val ObjectCls = typeOf[Object].typeSymbol // We need to know if the newtype is defined in an object so we can report @@ -123,7 +124,8 @@ private[macros] class NewTypeMacros(val c: blackbox.Context) maybeGenerateUnapplyMethod(clsDef, valDef, tparamsNoVar, tparamNames) ::: maybeGenerateOpsDef(clsDef, valDef, tparamsNoVar, tparamNames) ::: generateCoercibleInstances(tparamsNoVar, tparamNames, tparamsWild) ::: - generateDerivingMethods(tparamsNoVar, tparamNames, tparamsWild) + generateDerivingMethods(tparamsNoVar, tparamNames, tparamsWild) ::: + List(generateAsArrayInstance(clsDef, valDef, tparamsNoVar, tparamNames)) val newtypeObjParents = objParents :+ tq"$typesTraitName" val newtypeObjDef = q""" @@ -318,6 +320,37 @@ private[macros] class NewTypeMacros(val c: blackbox.Context) ) } + def generateAsArrayInstance( + clsDef: ClassDef, valDef: ValDef, tparamsNoVar: List[TypeDef], tparamNames: List[TypeName] + ): Tree = { + val Repr = tq"${valDef.tpt}" + val Type = if (tparamNames.isEmpty) tq"${clsDef.name}" else tq"${clsDef.name}[..$tparamNames]" + //val clsTagType = if (tparamNames.isEmpty) Repr else tq"$Repr forSome { ..$tparamsNoVar }" + summonImplicit(tq"$ClassTagCls[$Repr]") match { + case Some(Typed(ct, _)) => +// println(s"** FOUND IMPLICIT: ${showRaw(ct)}") + if (tparamNames.isEmpty) { + q"""implicit def asArray: $AsArrayObj.Aux[$Type, $Repr] = + $AsArrayObj.unsafeDerive[$Type, $Repr]($ct)""" + } else { + q"""implicit def asArray[..$tparamsNoVar]: $AsArrayObj.Aux[$Type, $Repr] = + $AsArrayObj.unsafeDerive[$Type, $Repr]($ct)""" + } + case _ => + if (tparamsNoVar.isEmpty) { + q"""implicit def asArray( + implicit ct: $ClassTagCls[$Repr] + ): $AsArrayObj.Aux[$Type, $Repr] = + $AsArrayObj.unsafeDerive[$Type, $Repr]""" + } else { + q"""implicit def asArray[..$tparamsNoVar]( + implicit ct: $ClassTagCls[$Repr] + ): $AsArrayObj.Aux[$Type, $Repr] = + $AsArrayObj.unsafeDerive[$Type, $Repr]""" + } + } + } + def getConstructor(body: List[Tree]): DefDef = body.collectFirst { case dd: DefDef if dd.name == termNames.CONSTRUCTOR => dd }.getOrElse(fail("Failed to locate constructor")) @@ -350,4 +383,13 @@ private[macros] class NewTypeMacros(val c: blackbox.Context) fail(s"newtypes do not support inheritance; illegal supertypes: ${unsupported.mkString(", ")}") } } + + /** Return the implicit value, if exists, for the given type `tpt`. */ + def summonImplicit(tpt: Tree): Option[Tree] = { + val typeResult = c.typecheck(tpt, c.TYPEmode, silent = true) + if (typeResult.isEmpty) None else { + val implicitResult = c.inferImplicitValue(typeResult.tpe) + if (implicitResult.isEmpty) None else Some(implicitResult) + } + } } diff --git a/shared/src/test/scala/io/estatico/newtype/macros/NewTypeMacrosTest.scala b/shared/src/test/scala/io/estatico/newtype/macros/NewTypeMacrosTest.scala index d20315c..188c979 100644 --- a/shared/src/test/scala/io/estatico/newtype/macros/NewTypeMacrosTest.scala +++ b/shared/src/test/scala/io/estatico/newtype/macros/NewTypeMacrosTest.scala @@ -1,5 +1,6 @@ package io.estatico.newtype.macros +import io.estatico.newtype.arrays.AsArray import org.scalatest.{FlatSpec, Matchers} import io.estatico.newtype.ops._ import org.scalacheck.Arbitrary @@ -27,7 +28,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers { it should "not generate an accessor method if private" in { // This is also useful so we can define local newtypes. - @newtype case class Foo0[A](private val value: A) + @newtype(debug=true) case class Foo0[A](private val value: A) Foo0('a') shouldBe 'a' assertDoesNotCompile("Foo0('a').value") } @@ -39,9 +40,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers { it should "work in arrays" in { val foo = Foo(313) - // See https://github.com/estatico/scala-newtype/issues/25 - // Array(foo).head shouldBe foo - Array[Int](313).asInstanceOf[Array[Foo]].head shouldBe foo + AsArray(foo).head shouldBe foo } behavior of "@newtype class" @@ -101,9 +100,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers { it should "work in arrays" in { val repr = Set(Option("newtypes")) val ot = OptionT(repr) - // See https://github.com/estatico/scala-newtype/issues/25 - // Array(ot).head shouldBe ot - Array(repr).asInstanceOf[Array[OptionT[Set, String]]].head shouldBe ot + AsArray(ot).head shouldBe ot } behavior of "@newtype with type bounds" @@ -365,7 +362,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers { object NewTypeMacrosTest { - @newtype case class Foo(value: Int) + @newtype(debug=true) case class Foo(value: Int) @newtype case class Nested(value: Foo)