Skip to content

Commit e9830f5

Browse files
committed
refs #10: Add NewTypeArray, support Array coercion
1 parent 7fde63c commit e9830f5

File tree

3 files changed

+276
-26
lines changed

3 files changed

+276
-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
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: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.estatico.newtype.macros
22

3-
import io.estatico.newtype.Coercible
3+
import io.estatico.newtype.{Coercible, NewTypeArray}
44
import scala.reflect.ClassTag
55
import scala.reflect.macros.blackbox
66

@@ -39,6 +39,8 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
3939

4040
val CoercibleCls = typeOf[Coercible[Nothing, Nothing]].typeSymbol
4141
val CoercibleObj = CoercibleCls.companion
42+
val NewTypeArrayCls = typeOf[NewTypeArray[Nothing]].typeSymbol
43+
val NewTypeArrayObj = NewTypeArrayCls.companion
4244
val ClassTagCls = typeOf[ClassTag[Nothing]].typeSymbol
4345
val ClassTagObj = ClassTagCls.companion
4446
val ObjectCls = typeOf[Object].typeSymbol
@@ -123,7 +125,8 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
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+
List(generateNewTypeArrayInstance(clsDef, valDef, tparamsNoVar, tparamNames))
127130

128131
val newtypeObjParents = objParents :+ tq"$typesTraitName"
129132
val newtypeObjDef = ModuleDef(
@@ -290,29 +293,49 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
290293
def generateCoercibleInstances(
291294
tparamsNoVar: List[TypeDef], tparamNames: List[TypeName], tparamsWild: List[TypeDef]
292295
): List[Tree] = {
293-
if (tparamsNoVar.isEmpty) List(
294-
q"@_root_.scala.inline implicit def unsafeWrap: $CoercibleCls[Repr, Type] = $CoercibleObj.instance",
295-
q"@_root_.scala.inline implicit def unsafeUnwrap: $CoercibleCls[Type, Repr] = $CoercibleObj.instance",
296-
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"
303-
) else List(
296+
val stdWraps = List(
304297
q"@_root_.scala.inline implicit def unsafeWrap[..$tparamsNoVar]: $CoercibleCls[Repr[..$tparamNames], Type[..$tparamNames]] = $CoercibleObj.instance",
305298
q"@_root_.scala.inline implicit def unsafeUnwrap[..$tparamsNoVar]: $CoercibleCls[Type[..$tparamNames], Repr[..$tparamNames]] = $CoercibleObj.instance",
306299
q"@_root_.scala.inline implicit def unsafeWrapM[M[_], ..$tparamsNoVar]: $CoercibleCls[M[Repr[..$tparamNames]], M[Type[..$tparamNames]]] = $CoercibleObj.instance",
307-
q"@_root_.scala.inline implicit def unsafeUnwrapM[M[_], ..$tparamsNoVar]: $CoercibleCls[M[Type[..$tparamNames]], M[Repr[..$tparamNames]]] = $CoercibleObj.instance",
300+
q"@_root_.scala.inline implicit def unsafeUnwrapM[M[_], ..$tparamsNoVar]: $CoercibleCls[M[Type[..$tparamNames]], M[Repr[..$tparamNames]]] = $CoercibleObj.instance"
301+
)
302+
val wrapKs = if (tparamsNoVar.isEmpty) Nil else List(
308303
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"
304+
q"@_root_.scala.inline implicit def unsafeUnwrapK[T[_[..$tparamsNoVar]]]: $CoercibleCls[T[Type], T[Repr]] = $CoercibleObj.instance"
315305
)
306+
stdWraps ++ wrapKs
307+
}
308+
309+
def generateNewTypeArrayInstance(
310+
clsDef: ClassDef, valDef: ValDef, tparamsNoVar: List[TypeDef], tparamNames: List[TypeName]
311+
): Tree = {
312+
val Repr = tq"${valDef.tpt}"
313+
val Type = if (tparamNames.isEmpty) tq"${clsDef.name}" else tq"${clsDef.name}[..$tparamNames]"
314+
summonImplicit(tq"$ClassTagCls[$Repr]") match {
315+
case Some(Typed(ct, _)) =>
316+
if (tparamNames.isEmpty) {
317+
q"""implicit val newtypeArray: $NewTypeArrayObj.Aux[$Type, $Repr] =
318+
$NewTypeArrayObj.unsafeDerive[$Type, $Repr]($ct)"""
319+
} else {
320+
q"""implicit def newtypeArray[..$tparamsNoVar]: $NewTypeArrayObj.Aux[$Type, $Repr] =
321+
__newtypeArray.asInstanceOf[$NewTypeArrayObj.Aux[$Type, $Repr]]
322+
private val __newtypeArray = $NewTypeArrayObj.unsafeDerive[Any, $Repr]($ct)"""
323+
}
324+
case _ =>
325+
q"""implicit def newtypeArray[..$tparamsNoVar](
326+
implicit ct: $ClassTagCls[$Repr]
327+
): $NewTypeArrayObj.Aux[$Type, $Repr] =
328+
$NewTypeArrayObj.unsafeDerive[$Type, $Repr]"""
329+
}
330+
}
331+
332+
/** Return the implicit value, if exists, for the given type `tpt`. */
333+
def summonImplicit(tpt: Tree): Option[Tree] = {
334+
val typeResult = c.typecheck(tpt, c.TYPEmode, silent = true)
335+
if (typeResult.isEmpty) None else {
336+
val implicitResult = c.inferImplicitValue(typeResult.tpe)
337+
if (implicitResult.isEmpty) None else Some(implicitResult)
338+
}
316339
}
317340

318341
def getConstructor(body: List[Tree]): DefDef = body.collectFirst {

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

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

3+
import io.estatico.newtype.NewTypeArray
34
import org.scalatest.{FlatSpec, Matchers}
45
import io.estatico.newtype.ops._
56
import org.scalacheck.Arbitrary
7+
import org.scalatest.exceptions.TestFailedException
8+
import scala.reflect.runtime.universe.WeakTypeTag
69

710
class NewTypeMacrosTest extends FlatSpec with Matchers {
811

@@ -39,9 +42,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers {
3942

4043
it should "work in arrays" in {
4144
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+
NewTypeArray(foo).apply(0) shouldBe foo
4546
}
4647

4748
behavior of "@newtype class"
@@ -102,9 +103,7 @@ class NewTypeMacrosTest extends FlatSpec with Matchers {
102103
import scala.collection.immutable.Set
103104
val repr = Set(Option("newtypes"))
104105
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
106+
Array(repr).coerce[Array[OptionT[Set, String]]].apply(0) shouldBe ot
108107
}
109108

110109
behavior of "@newtype with type bounds"
@@ -363,6 +362,200 @@ class NewTypeMacrosTest extends FlatSpec with Matchers {
363362
assertTypeError(""" 1 match { case Y1(x) => x }""")
364363
}
365364

365+
behavior of "NewTypeArray"
366+
367+
it should "work with @newtype X(Int)" in {
368+
@newtype case class X(private val value: Int)
369+
370+
val cls = scala.Predef.classOf[Array[Int]]
371+
val v: Int = 1
372+
373+
val a = NewTypeArray(X(v))
374+
assertCompiles("a: Array[X]")
375+
a.apply(0) shouldBe v
376+
a.getClass shouldBe cls
377+
378+
val a2 = NewTypeArray[X].downcast(a)
379+
assertCompiles("a2: Array[Int]")
380+
a2.apply(0) shouldBe v
381+
a2.getClass shouldBe cls
382+
383+
val a3 = NewTypeArray[X].upcast(a2)
384+
assertCompiles("a3: Array[X]")
385+
a3.apply(0) shouldBe v
386+
a3.getClass shouldBe cls
387+
388+
val a4 = a.coerce[Array[Int]]
389+
assertCompiles("a4: Array[Int]")
390+
a4.apply(0) shouldBe v
391+
a4.getClass shouldBe cls
392+
393+
val a5 = a4.coerce[Array[X]]
394+
assertCompiles("a5: Array[X]")
395+
a5.apply(0) shouldBe v
396+
a5.getClass shouldBe cls
397+
}
398+
399+
it should "work with @newsubtype X(Int)" in {
400+
@newsubtype case class X(private val value: Int)
401+
402+
val cls = scala.Predef.classOf[Array[Int]]
403+
val v: Int = 1
404+
405+
val a = NewTypeArray(X(v))
406+
assertCompiles("a: Array[X]")
407+
a.apply(0) shouldBe v
408+
a.getClass shouldBe cls
409+
410+
val a2 = NewTypeArray[X].downcast(a)
411+
assertCompiles("a2: Array[Int]")
412+
a2.apply(0) shouldBe v
413+
a2.getClass shouldBe cls
414+
415+
val a3 = NewTypeArray[X].upcast(a2)
416+
assertCompiles("a3: Array[X]")
417+
a3.apply(0) shouldBe v
418+
a3.getClass shouldBe cls
419+
420+
val a4 = a.coerce[Array[Int]]
421+
assertCompiles("a4: Array[Int]")
422+
a4.apply(0) shouldBe v
423+
a4.getClass shouldBe cls
424+
425+
val a5 = a4.coerce[Array[X]]
426+
assertCompiles("a5: Array[X]")
427+
a5.apply(0) shouldBe v
428+
a5.getClass shouldBe cls
429+
}
430+
431+
it should "work with @newtype X[A](List[A])" in {
432+
@newtype case class X[A](private val value: List[A])
433+
434+
val cls = scala.Predef.classOf[Array[List[Int]]]
435+
val v: List[Int] = List(1)
436+
437+
val a = NewTypeArray(X(v))
438+
assertCompiles("a: Array[X[Int]]")
439+
a.apply(0) shouldBe v
440+
a.getClass shouldBe cls
441+
442+
val a2 = NewTypeArray[X[Int]].downcast(a)
443+
assertCompiles("a2: Array[List[Int]]")
444+
a2.apply(0) shouldBe v
445+
a2.getClass shouldBe cls
446+
447+
val a3 = NewTypeArray[X[Int]].upcast(a2)
448+
assertCompiles("a3: Array[X[Int]]")
449+
a3.apply(0) shouldBe v
450+
a3.getClass shouldBe cls
451+
452+
val a4 = a.coerce[Array[List[Int]]]
453+
assertCompiles("a4: Array[List[Int]]")
454+
a4.apply(0) shouldBe v
455+
a4.getClass shouldBe cls
456+
457+
val a5 = a4.coerce[Array[X[Int]]]
458+
assertCompiles("a5: Array[X[Int]]")
459+
a5.apply(0) shouldBe v
460+
a5.getClass shouldBe cls
461+
}
462+
463+
it should "work with @newsubtype X[A](List[A])" in {
464+
@newsubtype case class X[A](private val value: List[A])
465+
466+
val cls = scala.Predef.classOf[Array[List[Int]]]
467+
val v: List[Int] = List(1)
468+
469+
val a = NewTypeArray(X(v))
470+
assertCompiles("a: Array[X[Int]]")
471+
a.apply(0) shouldBe v
472+
a.getClass shouldBe cls
473+
474+
val a2 = NewTypeArray[X[Int]].downcast(a)
475+
assertCompiles("a2: Array[List[Int]]")
476+
a2.apply(0) shouldBe v
477+
a2.getClass shouldBe cls
478+
479+
val a3 = NewTypeArray[X[Int]].upcast(a2)
480+
assertCompiles("a3: Array[X[Int]]")
481+
a3.apply(0) shouldBe v
482+
a3.getClass shouldBe cls
483+
484+
val a4 = a.coerce[Array[List[Int]]]
485+
assertCompiles("a4: Array[List[Int]]")
486+
a4.apply(0) shouldBe v
487+
a4.getClass shouldBe cls
488+
489+
val a5 = a4.coerce[Array[X[Int]]]
490+
assertCompiles("a5: Array[X[Int]]")
491+
a5.apply(0) shouldBe v
492+
a5.getClass shouldBe cls
493+
}
494+
495+
it should "work with @newtype X[F[_]](Functor[F])" in {
496+
@newtype case class X[F[_]](private val value: Functor[F])
497+
498+
val cls = scala.Predef.classOf[Array[Functor[List]]]
499+
val v: Functor[List] = Functor.list
500+
501+
val a = NewTypeArray(X(v))
502+
assertCompiles("a: Array[X[List]]")
503+
a.apply(0) shouldBe v
504+
a.getClass shouldBe cls
505+
506+
val a2 = NewTypeArray[X[List]].downcast(a)
507+
assertCompiles("a2: Array[Functor[List]]")
508+
a2.apply(0) shouldBe v
509+
a2.getClass shouldBe cls
510+
511+
val a3 = NewTypeArray[X[List]].upcast(a2)
512+
assertCompiles("a3: Array[X[List]]")
513+
a3.apply(0) shouldBe v
514+
a3.getClass shouldBe cls
515+
516+
val a4 = a.coerce[Array[Functor[List]]]
517+
assertCompiles("a4: Array[Functor[List]]")
518+
a4.apply(0) shouldBe v
519+
a4.getClass shouldBe cls
520+
521+
val a5 = a4.coerce[Array[X[List]]]
522+
assertCompiles("a5: Array[X[List]]")
523+
a5.apply(0) shouldBe v
524+
a5.getClass shouldBe cls
525+
}
526+
527+
it should "work with @newsubtype X[F[_]](Functor[F])" in {
528+
@newsubtype case class X[F[_]](private val value: Functor[F])
529+
530+
val cls = scala.Predef.classOf[Array[Functor[List]]]
531+
val v: Functor[List] = Functor.list
532+
533+
val a = NewTypeArray(X(v))
534+
assertCompiles("a: Array[X[List]]")
535+
a.apply(0) shouldBe v
536+
a.getClass shouldBe cls
537+
538+
val a2 = NewTypeArray[X[List]].downcast(a)
539+
assertCompiles("a2: Array[Functor[List]]")
540+
a2.apply(0) shouldBe v
541+
a2.getClass shouldBe cls
542+
543+
val a3 = NewTypeArray[X[List]].upcast(a2)
544+
assertCompiles("a3: Array[X[List]]")
545+
a3.apply(0) shouldBe v
546+
a3.getClass shouldBe cls
547+
548+
val a4 = a.coerce[Array[Functor[List]]]
549+
assertCompiles("a4: Array[Functor[List]]")
550+
a4.apply(0) shouldBe v
551+
a4.getClass shouldBe cls
552+
553+
val a5 = a4.coerce[Array[X[List]]]
554+
assertCompiles("a5: Array[X[List]]")
555+
a5.apply(0) shouldBe v
556+
a5.getClass shouldBe cls
557+
}
558+
366559
// Unfortunately, we don't have a way to assert on compiler warnings, which is
367560
// what happens with the code below. If we run with -Xfatal-warnings, the test
368561
// won't compile at all, so leaving here to do manual checking until scalatest

0 commit comments

Comments
 (0)