Skip to content

Typer regression for sum type derivation in paoloboni/binance-scala-client #24093

@WojciechMazur

Description

@WojciechMazur

Based on OpenCB failure in paoloboni/binance-scala-client - build logs

Compiler version

Last good release: 3.8.0-RC1-bin-20250822-658c8bd-NIGHTLY
First bad release: 3.8.0-RC1-bin-20250823-712d5bc-NIGHTLY
Bisect point to 712d5bc (unlikely directlly related, mostly likely migration to new Scala 3 stdlib)

Minimized code

import scala.deriving.*
import scala.compiletime.*

trait StringConverter[T]:
  def to(t: T): String

trait QueryParams
trait QueryParamsConverter[T]:
  def to(t: T): QueryParams

object QueryParamsConverter {
  extension [T](t: T)
    def toQueryParams(using converter: QueryParamsConverter[T]): QueryParams = converter.to(t)

  inline private def summonStringConverter[T]: StringConverter[T] = summonFrom {
    case converter: StringConverter[T] => converter
    case _ =>
      new StringConverter[T]:
        override def to(t: T): String = t.toString
  }

  inline def summonStringConverters[A <: Tuple]: List[StringConverter[?]] =
    inline erasedValue[A] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => summonStringConverter[t] :: summonStringConverters[ts]

  inline def summonQueryParamsConverters[A <: Tuple]: List[QueryParamsConverter[?]] =
    inline erasedValue[A] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => summonInline[QueryParamsConverter[t]] :: summonQueryParamsConverters[ts]

  inline given derived[T](using m: Mirror.Of[T]): QueryParamsConverter[T] = {
    val stringConverters = summonStringConverters[m.MirroredElemTypes]
    new QueryParamsConverter[T] {
      override def to(t: T): QueryParams =
        inline m match
          case p: Mirror.ProductOf[T] => ???
          case s: Mirror.SumOf[T] =>
            val _ = summonQueryParamsConverters[m.MirroredElemTypes]
            ???
    }
  }

}

object Test:
  sealed trait FutureOrderCreateParams
  object FutureOrderCreateParams:
    case class LIMIT(quantity: BigDecimal) extends FutureOrderCreateParams

  case class FutureOrderCreateResponse(clientOrderId: String)

  import QueryParamsConverter.*
  def createOrder(orderCreate: FutureOrderCreateParams) = orderCreate.toQueryParams

Output

Compiling project (Scala 3.8.0-RC1-bin-20250927-a4e1309-NIGHTLY, JVM (21))
-- [E172] Type Error: /Users/wmazur/projects/scala/community-build3/test.scala:54:83 --------------------------------------------------------------------------------------------------------
54 |  def createOrder(orderCreate: FutureOrderCreateParams) = orderCreate.toQueryParams
   |                                                                                   ^
   |                       No given instance of type QueryParamsConverter[FutureOrderCreateParams.LIMIT] was found.
   |                       I found:
   |
   |                           QueryParamsConverter.derived[FutureOrderCreateParams.LIMIT](
   |                             FutureOrderCreateParams.LIMIT.$asInstanceOf[
   |                               
   |                                 scala.deriving.Mirror.Product{
   |                                   type MirroredMonoType = FutureOrderCreateParams.LIMIT; type MirroredType = FutureOrderCreateParams.LIMIT; type MirroredLabel = ("LIMIT" : String);
   |                                     type MirroredElemTypes = BigDecimal *: EmptyTuple.type; type MirroredElemLabels = ("quantity" : String) *: EmptyTuple.type
   |                                 }
   |                               
   |                             ]
   |                           )
   |
   |                       But given instance derived in object QueryParamsConverter does not match type QueryParamsConverter[FutureOrderCreateParams.LIMIT].
   |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from test.scala:30
30 |      case _: (t *: ts)  => summonInline[QueryParamsConverter[t]] :: summonQueryParamsConverters[ts]
   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from test.scala:30
39 |            val _ = summonQueryParamsConverters[m.MirroredElemTypes]
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Expectation

Should compile

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions