Skip to content

Commit

Permalink
refs #10: Added AsArray type class
Browse files Browse the repository at this point in the history
  • Loading branch information
carymrobbins committed Apr 21, 2018
1 parent 6399e82 commit 855899c
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 10 deletions.
39 changes: 39 additions & 0 deletions shared/src/main/scala/io/estatico/newtype/arrays/AsArray.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.estatico.newtype.arrays

import scala.reflect.ClassTag

/*
/** Type class for building Arrays for newtype N. */
trait AsArray[N] {
/** Repr type of newtype N */
type Repr
def classTag: ClassTag[Repr]
final def empty: Array[N] = Array.empty(classTag).asInstanceOf[Array[N]]
final def apply(xs: N*): Array[N] =
Array(xs.asInstanceOf[Seq[Repr]]: _*)(classTag).asInstanceOf[Array[N]]
final def cast(array: Array[Repr]): Array[N] = array.asInstanceOf[Array[N]]
final def uncast(array: Array[N]): Array[Repr] = array.asInstanceOf[Array[Repr]]
}
object AsArray {
type Aux[R, N] = AsArray[N] { type Repr = R }
def empty[N](implicit arr: AsArray[N]): Array[N] = arr.empty
def apply[N](xs: N*)(implicit arr: AsArray[N]): Array[N] = arr(xs: _*)
def cast[R, N](a: Array[R])(implicit arr: AsArray.Aux[R, N]): Array[N] = arr.cast(a)
def unsafeDerive[R, N](implicit ct: ClassTag[R]): AsArray.Aux[R, N] = new AsArray[N] {
override type Repr = R
override def classTag: ClassTag[R] = ct
}
}
*/
67 changes: 67 additions & 0 deletions shared/src/main/scala/io/estatico/newtype/arrays/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.estatico.newtype

import scala.reflect.ClassTag

package object arrays {

// type AsArray[N] = AsArray.Type[N]
// object AsArray {
//
// type Type[N] <: Base with Tag
// type Base = Any { type Repr }
// trait Tag
// type Aux[N, R] = Type[N] { type Repr = R }
//
// private def clsTag[N](implicit ev: AsArray[N]): ClassTag[ev.Repr] =
// ev.asInstanceOf[ClassTag[ev.Repr]]
//
//
// def unsafeDerive[N, R](implicit ct: ClassTag[R]): Aux[N, R] = ct.asInstanceOf[Aux[N, R]]
//
// def empty[N: AsArray]: Array[N] = Array.empty(clsTag).asInstanceOf[Array[N]]
//
// def apply[N](xs: N*)(implicit ev: AsArray[N]): Array[N] =
// Array(xs.asInstanceOf[Seq[ev.Repr]]: _*)(clsTag).asInstanceOf[Array[N]]
//
// def upcast[R, N](array: Array[R])(implicit ev: Aux[N, R]): Array[N] =
// array.asInstanceOf[Array[N]]
//
// def downcast[N](array: Array[N])(implicit ev: AsArray[N]): Array[ev.Repr] =
// array.asInstanceOf[Array[ev.Repr]]
// }

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 }

private def clsTag[N](implicit ev: AsArray[N]): ClassTag[ev.Repr] = ev.clsTag

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)
}
}
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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")
}
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 855899c

Please sign in to comment.