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 23, 2018
1 parent 6399e82 commit 437a241
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 10 deletions.
45 changes: 45 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,45 @@
package io.estatico.newtype.arrays

import scala.reflect.ClassTag

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 upcast[N]: UpcastPartiallyApplied[N] = _upcast.asInstanceOf[UpcastPartiallyApplied[N]]

def downcast[N](array: Array[N])(implicit ev: AsArray[N]): Array[ev.Repr] = ev.downcast(array)

final class UpcastPartiallyApplied[N] private[AsArray] {
def apply[R](array: Array[R])(implicit ev: Aux[N, R]): Array[N] = upcast[R, N](array)
}

private val _upcast = new UpcastPartiallyApplied[Nothing]
}

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,36 @@ 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, _)) =>
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 +382,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 437a241

Please sign in to comment.