scala-newtype icon indicating copy to clipboard operation
scala-newtype copied to clipboard

Support Array casting

Open carymrobbins opened this issue 7 years ago • 2 comments

@alexknvl pointed out that it's possible to encode newtypes in such a way that casting Arrays with asInstanceOf will work (which means Coercible can work too).

Here's a simple adaptation from his solution to demonstrate a failing test case.

object Example {

  class Foo[A](val a: Array[A])

  val foo = new Foo(Array(1, 2, 3))

  def subst[F[_], T](fa: F[Int]): F[T] = fa.asInstanceOf[F[T]]

  type Good = Good.Type
  object Good {
    type Base <: Any
    trait Tag extends Any
    type Type <: Base with Tag
  }

  def good() = println(subst[Foo, Good](foo).a.head)

  @newtype case class Bad(value: Int)

  def bad() = println(subst[Foo, Bad](foo).a.head)

  def main(args: Array[String]): Unit = {
    good()
    bad()
  }
}

carymrobbins avatar Mar 01 '18 06:03 carymrobbins

/cc @joroKr21

The encoding of the newtype macros in v0.4.0 now allow for asInstanceOf casting by having the Base and Tag types extend Any, but of course this must be done with caution. Any time you use asInstanceOf you lose type safety, so it's important that you understand what you're doing.

scala> Array(1,2,3).asInstanceOf[Array[Foo]]
res0: Array[Foo] = Array(1, 2, 3)

scala> res0.getClass
res1: Class[_ <: Array[Foo]] = class [I

scala> res0.head
res2: Foo = 1

I have also started work on the 10-as-array branch which introduces the AsArray type class.

scala> import io.estatico.newtype.macros._, io.estatico.newtype.arrays._

scala> @newtype case class Foo(x: Int)

scala> AsArray(Foo(1), Foo(2))
res0: Array[Foo] = Array(1, 2)

scala> res0.getClass
res1: Class[_ <: Array[Foo]] = class [I

scala> res0.head
res2: Foo = 1

scala> AsArray.empty[Foo]
res3: Array[Foo] = Array()

scala> res3.getClass
res4: Class[_ <: Array[Foo]] = class [I

scala> AsArray.downcast(res0)
res8: Array[Int] = Array(1, 2)

scala> res8.head
res9: Int = 1

scala> Array(1,2,3)
res10: Array[Int] = Array(1, 2, 3)

scala> AsArray.upcast(res10): Array[Foo]
res12: Array[Foo] = Array(1, 2, 3)

scala> AsArray.upcast[Foo](res10)
res13: Array[Foo] = Array(1, 2, 3)

carymrobbins avatar Apr 23 '18 19:04 carymrobbins

I think we might be able to now support .coerce for Arrays. Should probably do extensive testing to ensure the current encoding won't blow up on us. If that's the case, then we should be able to omit the upcast and downcast methods on AsArray, leaving only the need for empty and apply.

carymrobbins avatar Apr 23 '18 19:04 carymrobbins