Search code examples
scalagenericsexistential-type

How can I resolve type error on compile: Any to T?


I have a json serializer for Tuple. It first reflects on the Tuple and builds a function that, given a Tuple of some arity, it will return a list of (TypeAdapter, value) pairs. (A TypeAdapter is a type-specific thing that renders the value.) Looks like this:

def extractTuple(p: Product): (Product)=>List[(TypeAdapter[_], Any)] = {
  val reflected = reflectOnTuple(tupleClass)  // extract a bunch of reflected metadata
  val tupleFieldInfo = reflected.tupleFieldInfo
  tupleFieldInfos match {
    case 1 =>
      (p: Product) => 
        List( (getTypeAdapterFor(tupleFieldInfo(0)), p.asInstanceOf[Tuple1[_]]._1) )
    case 2 =>
      (p: Product) => 
        List( (getTypeAdapterFor(tupleFieldInfo(0)), p.asInstanceOf[Tuple1[_]]._1),
          (getTypeAdapterFor(tupleFieldInfo(1)), p.asInstanceOf[Tuple1[_]]._2) )
    //... and so on to Tuple23
  }
}

In the JSON serializer I have a writeTuple() function that is shown below. In theory it should work as-is, but... I'm getting compile errors on fieldValue saying it is of type Any when what is expected is ?1.T.

A TypeAdapter looks like:

trait TypeAdapter[T] {
  def write[WIRE](
      t:      T,
      writer: Writer[WIRE],
      out:    mutable.Builder[WIRE, WIRE]): Unit
}
class JsonWriter() {
  def writeTuple[T](t: T, writeFn: (Product) => List[(TypeAdapter[_], Any)], out: mutable.Builder[JSON, JSON]): Unit = {
    out += "[".asInstanceOf[JSON]
    var first = true
    writeFn(t.asInstanceOf[Product]).foreach { case (fieldTA, fieldValue) =>
      if (first)
        first = false
      else
        out += ",".asInstanceOf[JSON]
      fieldTA.write(fieldValue, this, out) // <<-- this blows up (compile) on fieldValue because it's type Any, not some specific field Type
    }
    out += "]".asInstanceOf[JSON]
  }
}

How can I convince my TypeAdapter that the field is the correct type?


Solution

  • Dmytro, your answer is very close! Unfortunately neither the 1st or 2nd options compiled. I think the 3rd would have worked just fine, except... I'm actually using Dotty not Scala, and Dotty eliminated existential types.

    So I tried the following, and it worked. It involved modifying TypeAdapter, as it knows its type:

    trait TypeAdapter[T] {
      type tpe = T
      def write[WIRE](
          t:      T,
          writer: Writer[WIRE],
          out:    mutable.Builder[WIRE, WIRE]): Unit
      inline def castAndWrite[WIRE]( 
          v:      Any, 
          writer: Writer[WIRE], 
          out:    mutable.Builder[WIRE, WIRE]): Unit = 
        write(v.asInstanceOf[tpe], writer, out)
    }
    

    Calling castAndWrite() from JsonWriter enabled the correctly-typed write() mechanism to be called.