Skip to content

Record like HK definitions that compile fine under 3.6.4 crash under 3.7.0 #23293

Open
@rcano

Description

@rcano

Compiler version

3.7.0

Minimized code

I tried to minimize this by commenting parts out, but no luck (also most definitions depend on each other).

import scala.compiletime.ops.int.S
import scala.compiletime.ops.string.+
import scala.deriving.Mirror
import scala.annotation.{implicitNotFound}

object genrec {
  /** Fold a tuple just like Tuple.Fold but with typed upper bounds */
  type TupleFold[T <: Tuple, UpperBound, Z <: UpperBound, F[_ <: UpperBound, _ <: UpperBound] <: UpperBound] <: UpperBound = T match
    case EmptyTuple => Z
    case h *: t => F[Z, TupleFold[t, UpperBound, h, F]]

  
  type TupleReduce[Tup <: Tuple, UpperBound, F[_ <: UpperBound, _ <: UpperBound] <: UpperBound] <: UpperBound = Tup match {
    case h *: t => TupleFold[t, UpperBound, h, F]
  }

  /** Map a tuple just like Tuple.Map but with typed upper bounds */
  type TupleMap[T <: Tuple, UpperBound, F[_ <: UpperBound]] <: Tuple = T match
    case EmptyTuple => EmptyTuple
    case h *: t => F[h] *: TupleMap[t, UpperBound, F]


  /*erased*/ trait TypeWitness[N]:
    type T = N
  /*erased*/ given [T]: TypeWitness[T] = null
  object opaques {
    @scala.annotation.showAsInfix
    opaque type @@[S <: String & Singleton, +T] >: T = T

    extension [T <: String & Singleton](t: T) def @@[V](v: V): T @@ V = v
  }
  export opaques.*
  type FieldName[T <: @@[String & Singleton, Any]] <: String = T match {
    case @@[nme, _] => nme
  }
  type FieldNames[T <: Tuple] = TupleMap[T, @@[String & Singleton, Any], FieldName]
  type FieldType[T <: @@[String & Singleton, Any]] = T match {
    case @@[_, tpe] => tpe
  }

  type FieldToString[T <: @@[String & Singleton, Any]] <: String = T match {
    case @@[name, tpe] => name + ": " + tpe
  }

  extension [K <: String & Singleton, T](f: K @@ T) inline def value: T = f.asInstanceOf[T]

  type IndexOf[Rec <: Tuple, Field] <: Int = Rec match {
    case h *: t => h match { //use invariant type comparison by abusing Set trick
      case Field => 0
      case _ => S[IndexOf[t, Field]]
    }
  }
  type FieldToTuple[T <: @@[String & Singleton, Any]] <: Tuple = T match {
    case @@[nme, tpe] => (nme, tpe)
  }

  type IndexOfField[Rec <: Tuple, Field <: @@[String & Singleton, Any]] = IndexOf[Rec, Field]

  type WidenTuple[T <: Tuple] <: Tuple = T match {
    case EmptyTuple => EmptyTuple
    case h *: tail => h *: tail
  }

  given ProdOps: AnyRef with {
    extension [P <: Product](p: P)(using gen: Generic[P]) {
      def delField(field: String & Singleton)
          (using /*erased*/ w: TypeWitness[IndexOf[FieldNames[gen.Out], field.type]])
          (using i: ValueOf[w.T]): Tuple.Concat[Tuple.Take[gen.Out, w.T], Tuple.Drop[gen.Out, S[w.T]]] =
        val arr = scala.runtime.Tuples.toIArray(gen.toGen(p))
        val res = new Array[Object](arr.length - 1)
        System.arraycopy(arr, 0, res, 0, i.value)
        System.arraycopy(arr, i.value + 1, res, i.value, arr.length - i.value - 1)
        scala.runtime.Tuples.fromArray(res).asInstanceOf[Tuple.Concat[Tuple.Take[gen.Out, w.T], Tuple.Drop[gen.Out, S[w.T]]]]

      def renameField(field: String & Singleton, to: String & Singleton)
          (using /*erased*/ w: TypeWitness[IndexOf[FieldNames[gen.Out], field.type]])
          (using i: ValueOf[w.T]): Tuple.Concat[Tuple.Take[gen.Out, w.T], (to.type @@ FieldType[Tuple.Elem[gen.Out, w.T]]) *: Tuple.Drop[gen.Out, S[w.T]]] =
        gen.toGen(p).asInstanceOf[Tuple.Concat[Tuple.Take[gen.Out, w.T], (to.type @@ FieldType[Tuple.Elem[gen.Out, w.T]]) *: Tuple.Drop[gen.Out, S[w.T]]]]
      
      def +[K <: String & Singleton, V](field: K @@ V): Tuple.Concat[gen.Out, (K @@ V) *: EmptyTuple] =
        gen.toGen(p) ++ (field *: EmptyTuple)

      def replaceField[NewValueType](field: String & Singleton)
          (using /*erased*/ w: TypeWitness[IndexOf[FieldNames[gen.Out], field.type]])
          (using i: ValueOf[w.T])
          (f: FieldType[Tuple.Elem[gen.Out, w.T]] => NewValueType): Tuple.Concat[Tuple.Take[gen.Out, w.T], (field.type @@ NewValueType) *: Tuple.Drop[gen.Out, S[w.T]]] =
        val res = scala.runtime.Tuples.toArray(gen.toGen(p))
        val prev = res(i.value).asInstanceOf[FieldType[Tuple.Elem[gen.Out, w.T]]]
        val newv = f(prev)
        res(i.value) = newv.asInstanceOf[Object]
        scala.runtime.Tuples.fromArray(res).asInstanceOf[Tuple.Concat[Tuple.Take[gen.Out, w.T], (field.type @@ NewValueType) *: Tuple.Drop[gen.Out, S[w.T]]]]

      def toProd[Prd <: Product](using m: Mirror.ProductOf[Prd]): m.MirroredMonoType = m.fromProduct(gen.toGen(p))

      def select(fieldName: String & Singleton)(using s: SelectByName[fieldName.type, gen.Out]): s.Out = s.apply(gen.toGen(p))[fieldName.type]

      def field(fieldName: String & Singleton)
          (using /*erased*/ w: TypeWitness[IndexOf[FieldNames[gen.Out], fieldName.type]])
          (using i: ValueOf[w.T]): Tuple.Elem[gen.Out, w.T] =
        p.productElement(i.value).asInstanceOf[Tuple.Elem[gen.Out, w.T]]

      def pruneTo[Tup2 <: Tuple](using sr: SubRow[gen.Out, Tup2]): Tup2 = sr.asSubRecord(gen.toGen(p))

      def transmogrify[P2 <: Product](using gen2: Generic[P2], sr: SubRow[gen.Out, gen2.Out], m: Mirror.ProductOf[P2]): P2 = m.fromProduct(sr.asSubRecord(gen.toGen(p)))
    }
  }

  trait Generic[Prod <: Product] {
    type Out <: Tuple
    def toGen(prod: Prod): Out

    extension (prod: Prod) def gen: Out = toGen(prod)
  }
  object Generic extends GenericLowPriority {
    type Aux[Prod <: Product, _Out <: Tuple] = Generic[Prod] { type Out = _Out}

    transparent inline def of[Prod <: Product] = scala.compiletime.summonInline[Generic[Prod]]

    type IsRecord[Rec <: Tuple] = Rec match {
      case EmptyTuple => true
      case a *: tail => a match {
        case @@[?, ?] => IsRecord[tail]
        case _ => false
      }
    }

    private val identityGeneric = new Generic[Tuple] {
      type Out = Tuple
      def toGen(t: Tuple) = t
    }

    given tuplesGeneric[Rec <: Tuple](using IsRecord[Rec] =:= true): Generic.Aux[Rec, Rec] = identityGeneric.asInstanceOf[Generic.Aux[Rec, Rec]]
  }
  trait GenericLowPriority { self: Generic.type =>
    given productsGeneric[Prod <: Product](using m: Mirror.ProductOf[Prod]): Generic.Aux[
      Prod,
      WidenTuple[Tuple.Map[Tuple.Zip[m.MirroredElemLabels, m.MirroredElemTypes], [Entry] =>> Entry match {
        case (nme, tpe) => nme @@ tpe
      }]]
    ] = new Generic[Prod] {
      type Out = WidenTuple[Tuple.Map[Tuple.Zip[m.MirroredElemLabels, m.MirroredElemTypes], [Entry] =>> Entry match {
        case (nme, tpe) => nme @@ tpe
      }]]
      def toGen(prod: Prod): Out =
        val r = prod.productIterator.map(v => ("s" @@ v).asInstanceOf[Object]).toArray
        scala.runtime.Tuples.fromArray(r).asInstanceOf[Out]
    }
  }

  type FieldSet[Rec <: Tuple] = Tuple.Union[Tuple.Map[Rec, FieldToTuple]]
  type MissingFields[Rec <: Tuple, AvailableFields] <: Tuple = Rec match {
    case EmptyTuple => EmptyTuple
    case h *: t => FieldToTuple[h] match {
      case AvailableFields => MissingFields[t, AvailableFields]
      case _ => h *: MissingFields[t, AvailableFields]
    }
  }


  /** Read field [[Field]]  from [[Rec]] */
  @implicitNotFound("Field with name ${Field} is not present in ${Rec}")
  trait SelectByName[Field <: String & Singleton, Rec <: Tuple] {
    type Out
    extension (r: Rec) def apply[F <: Field]: Out
  }
  object SelectByName {
    type Aux[Field <: String & Singleton, Rec <: Tuple, O] = SelectByName[Field, Rec] { type Out = O }

    given [T, Field <: String & Singleton, Rec <: Tuple]
        // (using /*erased*/ w: TypeWitness[IndexOf[TupleMap[Rec, @@[String & Singleton, Any], FieldName], Field]])
        (using /*erased*/ w: TypeWitness[IndexOf[Tuple.Map[Rec, FieldName], Field]])
        (using v: ValueOf[w.T]): Aux[Field, Rec, FieldType[Tuple.Elem[Rec, w.T]]] = new SelectByName[Field, Rec] {
      type Out = FieldType[Tuple.Elem[Rec, w.T]]
      extension (r: Rec) def apply[F <: Field] = r.productElement(v.value).asInstanceOf[@@[Field, Out]].value
    }
  }

  /** Typeclass denoting that [[T]] is a subrow of [[Rec]], this allows to select every field from [[Rec]] in [[T]] */
  @implicitNotFound("can't prove that ${T} is a subrow of ${Rec}")
  trait SubRow[T, Rec <: Tuple] {

    /** Read [[FieldName] value
      * Note: the SelectByName is /*erased*/, it's only there to ensure via the compiler that you're trying to read one of the
      * fields defined in the SubRow
      */
    extension (r: T)
      def apply[FieldName <: String & Singleton](using ValueOf[FieldName])(using /*erased*/ s: SelectByName[FieldName, Rec]): s.Out
      def asSubRecord: Rec
  }
  object SubRow {
    import scala.compiletime.*
    type Of[Rec <: Tuple] = [T] =>> SubRow[T, Rec]

    /** Developer API, don't use directly */
    class SubRowImpl[T <: Product, Rec <: Tuple](val fieldIndices: collection.immutable.SeqMap[String, Int], val recFields: IArray[String]) extends SubRow[T, Rec] {
      extension (r: T)
        def apply[FieldName <: String & Singleton](using ValueOf[FieldName])(using /*erased*/ s: SelectByName[FieldName, Rec]): s.Out =
          val i = fieldIndices(valueOf[FieldName])
          r.productElement(i).asInstanceOf[s.Out]

        def asSubRecord: Rec =
          val resContent = new Array[Object](recFields.size)
          recFields.zipWithIndex `foreach` ((f, i) => resContent(i) = r.productElement(fieldIndices(f)).asInstanceOf[Object])
          scala.runtime.Tuples.fromArray(resContent).asInstanceOf[Rec]
    }

    transparent inline given isSubRow[T <: Product, Rec <: Tuple]
      (using gen: Generic[T]): SubRow[T, Rec] = {
      inline erasedValue[MissingFields[Rec, FieldSet[gen.Out]]] match {
        case t: NonEmptyTuple =>
          inline val mf = valueOf[TupleReduce[FieldNames[t.type], String, [a <: String, b <: String] =>> a + "\n  " + b]]
          error("You have missing fields:\n  " + mf + "\nEnsure that these fields are present and that their types match")
          // error("You have missing fields:\nEnsure that these fields are present and that their types match")
        case _ =>
      }

      val labels = summonAll[TupleMap[gen.Out, @@[String & Singleton, Any], [x <: @@[String & Singleton, Any]] =>> ValueOf[FieldName[x]]]].toArray
      val recFields = summonAll[TupleMap[Rec, @@[String & Singleton, Any], [x <: @@[String & Singleton, Any]] =>> ValueOf[FieldName[x]]]].toIArray.map(_.asInstanceOf[ValueOf[String]].value)
      (SubRowImpl[T, Rec](labels.map(_.asInstanceOf[ValueOf[String]].value).zipWithIndex.to(collection.immutable.TreeSeqMap), recFields): SubRow[T, Rec])
    }

  }
}

Output (click arrow to expand)

unhandled exception while running MegaPhase{crossVersionChecks, firstTransform, checkReentrant, elimPackagePrefixes, cookComments, checkLoopingImplicits, betaReduce, inlineVals, expandSAMs, elimRepeated, refchecks, dropForMap} on /tmp/genrec/genrec.scala

An unhandled exception was thrown in the compiler.
Please file a crash report here:
https://github.com/scala/scala3/issues/new/choose
For non-enriched exceptions, compile with -Xno-enrich-error-messages.

 while compiling: /tmp/genrec/genrec.scala
    during phase: MegaPhase{crossVersionChecks, firstTransform, checkReentrant, elimPackagePrefixes, cookComments, checkLoopingImplicits, betaReduce, inlineVals, expandSAMs, elimRepeated, refchecks, dropForMap}
            mode: Mode(ImplicitsEnabled)
 library version: version 2.13.16
compiler version: version 3.7.0
        settings: -classpath /home/user/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.7.0/scala3-library_3-3.7.0.jar:/home/user/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.16/scala-library-2.13.16.jar -d /tmp/genrec/.scala-build/genrec_71ae519d80/classes/main -sourceroot /tmp/genrec

Exception in thread "main" java.lang.AssertionError: assertion failed: TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Nothing),TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class )),object scala),trait Tuple))
at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
at dotty.tools.dotc.core.Types$TypeBounds.(Types.scala:5554)
at dotty.tools.dotc.core.Types$RealTypeBounds.(Types.scala:5631)
at dotty.tools.dotc.core.Types$TypeBounds$.apply(Types.scala:5672)
at dotty.tools.dotc.core.Types$TypeBounds.derivedTypeBounds(Types.scala:5562)
at dotty.tools.dotc.core.Types$ApproximatingTypeMap.derivedTypeBounds(Types.scala:6503)
at dotty.tools.dotc.core.Types$TypeMap.mapOver(Types.scala:6215)
at dotty.tools.dotc.core.TypeOps$AsSeenFromMap.apply(TypeOps.scala:111)
at dotty.tools.dotc.core.TypeOps$AsSeenFromMap.apply(TypeOps.scala:69)
at scala.collection.immutable.List.mapConserve(List.scala:473)
at dotty.tools.dotc.core.Types$TypeMap.mapOverLambda(Types.scala:6158)
at dotty.tools.dotc.core.TypeOps$AsSeenFromMap.apply(TypeOps.scala:105)
at dotty.tools.dotc.core.TypeOps$.asSeenFrom(TypeOps.scala:55)
at dotty.tools.dotc.core.Types$Type.asSeenFrom(Types.scala:1113)
at dotty.tools.dotc.core.Denotations$SingleDenotation.derived$1(Denotations.scala:1107)
at dotty.tools.dotc.core.Denotations$SingleDenotation.computeAsSeenFrom(Denotations.scala:1134)
at dotty.tools.dotc.core.Denotations$SingleDenotation.computeAsSeenFrom(Denotations.scala:1087)
at dotty.tools.dotc.core.Denotations$PreDenotation.asSeenFrom(Denotations.scala:137)
at dotty.tools.dotc.core.SymDenotations$ClassDenotation.findMember(SymDenotations.scala:2194)
at dotty.tools.dotc.core.Types$Type.go$1(Types.scala:778)
at dotty.tools.dotc.core.Types$Type.findMember(Types.scala:959)
at dotty.tools.dotc.core.Types$Type.memberBasedOnFlags(Types.scala:751)
at dotty.tools.dotc.core.Types$Type.nonPrivateMember(Types.scala:741)
at dotty.tools.dotc.typer.RefChecks$.hidden$1(RefChecks.scala:1190)
at dotty.tools.dotc.typer.RefChecks$.checkExtensionMethods(RefChecks.scala:1202)
at dotty.tools.dotc.typer.RefChecks.transformDefDef(RefChecks.scala:1356)
at dotty.tools.dotc.typer.RefChecks.transformDefDef(RefChecks.scala:1351)
at dotty.tools.dotc.transform.MegaPhase.goDefDef(MegaPhase.scala:1041)
at dotty.tools.dotc.transform.MegaPhase.goDefDef(MegaPhase.scala:1042)
at dotty.tools.dotc.transform.MegaPhase.goDefDef(MegaPhase.scala:1042)
at dotty.tools.dotc.transform.MegaPhase.goDefDef(MegaPhase.scala:1042)
at dotty.tools.dotc.transform.MegaPhase.goDefDef(MegaPhase.scala:1042)
at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:268)
at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
at dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
at dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:376)
at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:272)
at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
at dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
at dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:376)
at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:272)
at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
at dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
at dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
at dotty.tools.dotc.transform.MegaPhase.mapPackage$1(MegaPhase.scala:396)
at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:399)
at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
at dotty.tools.dotc.transform.MegaPhase.transformUnit(MegaPhase.scala:481)
at dotty.tools.dotc.transform.MegaPhase.run(MegaPhase.scala:493)
at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:383)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
at scala.collection.immutable.List.foreach(List.scala:334)
at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:376)
at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:367)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1324)
at dotty.tools.dotc.Run.runPhases$1(Run.scala:360)
at dotty.tools.dotc.Run.compileUnits$$anonfun$1$$anonfun$2(Run.scala:407)
at dotty.tools.dotc.Run.compileUnits$$anonfun$1$$anonfun$adapted$1(Run.scala:407)
at scala.Function0.apply$mcV$sp(Function0.scala:42)
at dotty.tools.dotc.Run.showProgress(Run.scala:469)
at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:407)
at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:419)
at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:69)
at dotty.tools.dotc.Run.compileUnits(Run.scala:419)
at dotty.tools.dotc.Run.compileSources(Run.scala:306)
at dotty.tools.dotc.Run.compile(Run.scala:291)
at dotty.tools.dotc.Driver.doCompile(Driver.scala:37)
at dotty.tools.dotc.Driver.process(Driver.scala:201)
at dotty.tools.dotc.Driver.process(Driver.scala:169)
at dotty.tools.dotc.Driver.process(Driver.scala:181)
at dotty.tools.dotc.Driver.main(Driver.scala:211)
at dotty.tools.dotc.Main.main(Main.scala)
Compilation failed

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions