Skip to content

Commit 40ae81d

Browse files
committed
Generate NewTypeArray instances for 2.11+ only
1 parent 7fde63c commit 40ae81d

File tree

6 files changed

+297
-26
lines changed

6 files changed

+297
-26
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.estatico.newtype.arrays
2+
3+
import scala.reflect.ClassTag
4+
5+
/** Type class for building arrays for newtypes. */
6+
trait NewTypeArray[N] {
7+
type Repr
8+
def clsTag: ClassTag[Repr]
9+
10+
final def empty: Array[N] = Array.empty(clsTag).asInstanceOf[Array[N]]
11+
12+
final def apply(xs: N*): Array[N] =
13+
Array(xs.asInstanceOf[Seq[Repr]]: _*)(clsTag).asInstanceOf[Array[N]]
14+
15+
final def upcast(a: Array[Repr]): Array[N] = a.asInstanceOf[Array[N]]
16+
17+
final def downcast(a: Array[N]): Array[Repr] = a.asInstanceOf[Array[Repr]]
18+
}
19+
20+
object NewTypeArray {
21+
22+
type Aux[N, R] = NewTypeArray[N] { type Repr = R }
23+
24+
def apply[N](implicit ev: NewTypeArray[N]): Aux[N, ev.Repr] = ev
25+
26+
def apply[N](xs: N*)(implicit ev: NewTypeArray[N]): Array[N] = ev(xs: _*)
27+
28+
def empty[N](implicit ev: NewTypeArray[N]): Array[N] = ev.empty
29+
30+
def unsafeDerive[N, R](implicit ct: ClassTag[R]): Aux[N, R] = new NewTypeArray[N] {
31+
type Repr = R
32+
override def clsTag: ClassTag[Repr] = ct
33+
}
34+
}

shared/src/main/scala/io/estatico/newtype/macros/NewTypeMacros.scala

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.estatico.newtype.macros
22

33
import io.estatico.newtype.Coercible
4-
import scala.reflect.ClassTag
54
import scala.reflect.macros.blackbox
65

76
//noinspection TypeAnnotation
@@ -39,8 +38,6 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
3938

4039
val CoercibleCls = typeOf[Coercible[Nothing, Nothing]].typeSymbol
4140
val CoercibleObj = CoercibleCls.companion
42-
val ClassTagCls = typeOf[ClassTag[Nothing]].typeSymbol
43-
val ClassTagObj = ClassTagCls.companion
4441
val ObjectCls = typeOf[Object].typeSymbol
4542

4643
// We need to know if the newtype is defined in an object so we can report
@@ -118,12 +115,18 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
118115
val reprType = valDef.tpt
119116
val typesTraitName = TypeName(s"${clsName.decodedName}__Types")
120117
val tparams = clsDef.tparams
118+
val extra = generateExtra(
119+
clsDef, modDef, valDef,
120+
tparamsNoVar, tparamNames, tparamsWild,
121+
subtype
122+
)
121123
val companionExtraDefs =
122124
maybeGenerateApplyMethod(clsDef, valDef, tparamsNoVar, tparamNames) :::
123125
maybeGenerateUnapplyMethod(clsDef, valDef, tparamsNoVar, tparamNames) :::
124126
maybeGenerateOpsDef(clsDef, valDef, tparamsNoVar, tparamNames) :::
125127
generateCoercibleInstances(tparamsNoVar, tparamNames, tparamsWild) :::
126-
generateDerivingMethods(tparamsNoVar, tparamNames, tparamsWild)
128+
generateDerivingMethods(tparamsNoVar, tparamNames, tparamsWild) :::
129+
extra
127130

128131
val newtypeObjParents = objParents :+ tq"$typesTraitName"
129132
val newtypeObjDef = ModuleDef(
@@ -294,24 +297,14 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
294297
q"@_root_.scala.inline implicit def unsafeWrap: $CoercibleCls[Repr, Type] = $CoercibleObj.instance",
295298
q"@_root_.scala.inline implicit def unsafeUnwrap: $CoercibleCls[Type, Repr] = $CoercibleObj.instance",
296299
q"@_root_.scala.inline implicit def unsafeWrapM[M[_]]: $CoercibleCls[M[Repr], M[Type]] = $CoercibleObj.instance",
297-
q"@_root_.scala.inline implicit def unsafeUnwrapM[M[_]]: $CoercibleCls[M[Type], M[Repr]] = $CoercibleObj.instance",
298-
// Avoid ClassCastException with Array types by prohibiting Array coercing.
299-
q"@_root_.scala.inline implicit def cannotWrapArrayAmbiguous1: $CoercibleCls[_root_.scala.Array[Repr], _root_.scala.Array[Type]] = $CoercibleObj.instance",
300-
q"@_root_.scala.inline implicit def cannotWrapArrayAmbiguous2: $CoercibleCls[_root_.scala.Array[Repr], _root_.scala.Array[Type]] = $CoercibleObj.instance",
301-
q"@_root_.scala.inline implicit def cannotUnwrapArrayAmbiguous1: $CoercibleCls[_root_.scala.Array[Type], _root_.scala.Array[Repr]] = $CoercibleObj.instance",
302-
q"@_root_.scala.inline implicit def cannotUnwrapArrayAmbiguous2: $CoercibleCls[_root_.scala.Array[Type], _root_.scala.Array[Repr]] = $CoercibleObj.instance"
300+
q"@_root_.scala.inline implicit def unsafeUnwrapM[M[_]]: $CoercibleCls[M[Type], M[Repr]] = $CoercibleObj.instance"
303301
) else List(
304302
q"@_root_.scala.inline implicit def unsafeWrap[..$tparamsNoVar]: $CoercibleCls[Repr[..$tparamNames], Type[..$tparamNames]] = $CoercibleObj.instance",
305303
q"@_root_.scala.inline implicit def unsafeUnwrap[..$tparamsNoVar]: $CoercibleCls[Type[..$tparamNames], Repr[..$tparamNames]] = $CoercibleObj.instance",
306304
q"@_root_.scala.inline implicit def unsafeWrapM[M[_], ..$tparamsNoVar]: $CoercibleCls[M[Repr[..$tparamNames]], M[Type[..$tparamNames]]] = $CoercibleObj.instance",
307305
q"@_root_.scala.inline implicit def unsafeUnwrapM[M[_], ..$tparamsNoVar]: $CoercibleCls[M[Type[..$tparamNames]], M[Repr[..$tparamNames]]] = $CoercibleObj.instance",
308306
q"@_root_.scala.inline implicit def unsafeWrapK[T[_[..$tparamsNoVar]]]: $CoercibleCls[T[Repr], T[Type]] = $CoercibleObj.instance",
309-
q"@_root_.scala.inline implicit def unsafeUnwrapK[T[_[..$tparamsNoVar]]]: $CoercibleCls[T[Type], T[Repr]] = $CoercibleObj.instance",
310-
// Avoid ClassCastException with Array types by prohibiting Array coercing.
311-
q"@_root_.scala.inline implicit def cannotWrapArrayAmbiguous1[..$tparamsNoVar]: $CoercibleCls[_root_.scala.Array[Repr[..$tparamNames]], _root_.scala.Array[Type[..$tparamNames]]] = $CoercibleObj.instance",
312-
q"@_root_.scala.inline implicit def cannotWrapArrayAmbiguous2[..$tparamsNoVar]: $CoercibleCls[_root_.scala.Array[Repr[..$tparamNames]], _root_.scala.Array[Type[..$tparamNames]]] = $CoercibleObj.instance",
313-
q"@_root_.scala.inline implicit def cannotUnwrapArrayAmbiguous1[..$tparamsNoVar]: $CoercibleCls[_root_.scala.Array[Type[..$tparamNames]], _root_.scala.Array[Repr[..$tparamNames]]] = $CoercibleObj.instance",
314-
q"@_root_.scala.inline implicit def cannotUnwrapArrayAmbiguous2[..$tparamsNoVar]: $CoercibleCls[_root_.scala.Array[Type[..$tparamNames]], _root_.scala.Array[Repr[..$tparamNames]]] = $CoercibleObj.instance"
307+
q"@_root_.scala.inline implicit def unsafeUnwrapK[T[_[..$tparamsNoVar]]]: $CoercibleCls[T[Type], T[Repr]] = $CoercibleObj.instance"
315308
)
316309
}
317310

shared/src/main/scala_2.10/io/estatico/newtype/macros/NewTypeCompatMacros.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,12 @@ trait NewTypeCompatMacros {
2525
* @return
2626
*/
2727
def opsClsParent: Symbol = typeOf[AnyRef].typeSymbol
28+
29+
/** scala 2.10 has problems with NewTypeArray, so we'll only generate it for 2.11+ */
30+
def generateExtra(
31+
clsDef: ClassDef, modDef: ModuleDef, valDef: ValDef,
32+
tparamsNoVar: List[TypeDef], tparamNames: List[TypeName], tparamsWild: List[TypeDef],
33+
subtype: Boolean
34+
): List[Tree] = Nil
2835
}
2936

shared/src/main/scala_2.11+/io/estatico/newtype/macros/NewTypeCompatMacros.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.estatico.newtype.macros
22

3+
import io.estatico.newtype.arrays.NewTypeArray
4+
import scala.reflect.ClassTag
35
import scala.reflect.macros.blackbox
46

57
trait NewTypeCompatMacros {
@@ -9,4 +11,43 @@ trait NewTypeCompatMacros {
911
import c.universe._
1012

1113
def opsClsParent: Symbol = typeOf[AnyVal].typeSymbol
14+
15+
def generateExtra(
16+
clsDef: ClassDef, modDef: ModuleDef, valDef: ValDef,
17+
tparamsNoVar: List[TypeDef], tparamNames: List[TypeName], tparamsWild: List[TypeDef],
18+
subtype: Boolean
19+
): List[Tree] = {
20+
val Repr = tq"${valDef.tpt}"
21+
val Type = if (tparamNames.isEmpty) tq"${clsDef.name}" else tq"${clsDef.name}[..$tparamNames]"
22+
val genNewTypeArray = summonImplicit(tq"$ClassTagCls[$Repr]") match {
23+
case Some(Typed(ct, _)) =>
24+
if (tparamNames.isEmpty) {
25+
q"""implicit val newtypeArray: $NewTypeArrayObj.Aux[$Type, $Repr] =
26+
$NewTypeArrayObj.unsafeDerive[$Type, $Repr]($ct)"""
27+
} else {
28+
q"""implicit def newtypeArray[..$tparamsNoVar]: $NewTypeArrayObj.Aux[$Type, $Repr] =
29+
__newtypeArray.asInstanceOf[$NewTypeArrayObj.Aux[$Type, $Repr]]
30+
private val __newtypeArray = $NewTypeArrayObj.unsafeDerive[Any, $Repr]($ct)"""
31+
}
32+
case _ =>
33+
q"""implicit def newtypeArray[..$tparamsNoVar](
34+
implicit ct: $ClassTagCls[$Repr]
35+
): $NewTypeArrayObj.Aux[$Type, $Repr] =
36+
$NewTypeArrayObj.unsafeDerive[$Type, $Repr](ct)"""
37+
}
38+
List(genNewTypeArray)
39+
}
40+
41+
private val ClassTagCls = typeOf[ClassTag[Nothing]].typeSymbol
42+
private val NewTypeArrayCls = typeOf[NewTypeArray[Nothing]].typeSymbol
43+
private val NewTypeArrayObj = NewTypeArrayCls.companion
44+
45+
/** Return the implicit value, if exists, for the given type `tpt`. */
46+
private def summonImplicit(tpt: Tree): Option[Tree] = {
47+
val typeResult = c.typecheck(tpt, c.TYPEmode, silent = true)
48+
if (typeResult.isEmpty) None else {
49+
val implicitResult = c.inferImplicitValue(typeResult.tpe)
50+
if (implicitResult.isEmpty) None else Some(implicitResult)
51+
}
52+
}
1253
}

shared/src/test/scala/io/estatico/newtype/macros/NewTypeMacrosTest.scala

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package io.estatico.newtype.macros
33
import org.scalatest.{FlatSpec, Matchers}
44
import io.estatico.newtype.ops._
55
import org.scalacheck.Arbitrary
6+
import org.scalatest.exceptions.TestFailedException
7+
import scala.reflect.runtime.universe.WeakTypeTag
68

79
class NewTypeMacrosTest extends FlatSpec with Matchers {
810

@@ -37,13 +39,6 @@ class NewTypeMacrosTest extends FlatSpec with Matchers {
3739
res shouldBe 4
3840
}
3941

40-
it should "work in arrays" in {
41-
val foo = Foo(313)
42-
// See https://github.com/estatico/scala-newtype/issues/25
43-
// Array(foo).apply(0) shouldBe foo
44-
Array[Int](313).asInstanceOf[Array[Foo]].apply(0) shouldBe foo
45-
}
46-
4742
behavior of "@newtype class"
4843

4944
it should "not expose a default constructor" in {
@@ -102,9 +97,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers {
10297
import scala.collection.immutable.Set
10398
val repr = Set(Option("newtypes"))
10499
val ot = OptionT(repr)
105-
// See https://github.com/estatico/scala-newtype/issues/25
106-
// Array(ot).apply(0) shouldBe ot
107-
Array(repr).asInstanceOf[Array[OptionT[Set, String]]].apply(0) shouldBe ot
100+
Array(repr).coerce[Array[OptionT[Set, String]]].apply(0) shouldBe ot
108101
}
109102

110103
behavior of "@newtype with type bounds"

0 commit comments

Comments
 (0)