Search code examples
scalagenericsscala-3match-types

Scala 3 'cast' generic type to match type


Recently I started to port a simple ORM from Scala 2 to Scala 3 using generics and match types. After struggling with the type projection (see How to overcome Scala 3 missing type projection?) I now stuck in type chaos ;).

A simplified version of code looks like

trait IdEntity:
  type ID_Type
  val idDefault: ID_Type
  val id: Option[ID_Type]
  def idOrDefault: ID_Type = id.getOrElse(idDefault)
object IdEntity:
  type Aux[K] = IdEntity {type ID_Type = K}

type IdType[T <: IdEntity] = T match
  case IdEntity.Aux[k] => k

trait IdRepository[E <: IdEntity]:
  type ID_Type = IdType[E]
  type TE = E {type ID_Type = IdType[E]}

  def delete(id: ID_Type): Either[String, Int] = deleteImpl(id)

  def delete(entity: TE): Either[String, Int] = delete(entity.idOrDefault)

  def update(entity: TE): Int = {
    findById(entity.id) match {
      case None => 0
      case Some(e) => updateImpl(e) // type error E is not TE
    }
  }

  def findById(id: Option[ID_Type]): Option[E]
  
  protected def deleteImpl(id: ID_Type): Either[String, Int]
  
  protected def updateImpl(entity: TE): Int

https://scastie.scala-lang.org/lOZYn2o5TLiW1CwIvTdFIw

As you can see I use match types for the ID of which is fine so fare and use a type TE which is the generic E with match typed ID. Now I have the problem that the basic methods like findById returns the generic E but if I need to reuse I need TE instead.

Since both are the same as runtime a asInstanceOf[TE] work but this is not what I prefer.

Is there a way to 'cast' an instance of E to TE since they are the same at runtime?

I already tried TypeTest but this does not work since I work with generics here.


Solution

  • You can try to change the signature of findById (using TE instead of E wherever possible):

    trait IdRepository[E <: IdEntity]:
      ...
      def findById(id: Option[ID_Type]): Option[TE]
    

    https://scastie.scala-lang.org/DmytroMitin/vuZAH5crQZSIyHUOwyCfJQ

    or use an implicit hint in update (postponing checking that E =:= TE till when E is inferred to be a concrete type):

    import scala.compiletime.summonFrom
    
    trait IdRepository[E <: IdEntity]:
      ...
    
      inline def update(entity: TE): Int = {
        findById(entity.id) match {
          case None => 0
          case Some(e) => summonFrom {
            case _: (E =:= TE) => updateImpl(e)
          }
        }
      }
    

    https://scastie.scala-lang.org/DmytroMitin/vuZAH5crQZSIyHUOwyCfJQ/1

    or

    trait IdRepository[E <: IdEntity]:
      ...
    
      def update(entity: TE)(using E =:= TE): Int = {
        findById(entity.id) match {
          case None => 0
          case Some(e) => updateImpl(e)
        }
      }
    

    https://scastie.scala-lang.org/DmytroMitin/vuZAH5crQZSIyHUOwyCfJQ/2


    How to define a scala.ValueOf for tuples in scala 3?

    How to prove that `Tuple.Map[H *: T, F] =:= (F[H] *: Tuple.Map[T, F])` in Scala 3

    scala 3 map tuple to futures of tuple types and back