Record like HK definitions that compile fine under 3.6.4 crash under 3.7.0
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)
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
Actually, there's a warning emitted by 3.6.4:
-- [E194] Potential Issue Warning: /tmp/genrec/genrec.scala:164:27 -------------
164 | extension (r: Rec) def apply[F <: Field]: Out
| ^
|Extension method apply will never be selected from type Rec
|because Rec already has a member with the same name and compatible parameter types.
|
| longer explanation available when compiling with `-explain`
1 warning found
turns out addressing that warning by renaming that extension method makes this compile under 3.7.0 too. Still would be nice for the compiler not to crash.
Thanks for the hint. The check is looking up the member in the receiver.
The failing check is for the apply in trait SelectByName on Rec <: Tuple.
There is a more thorough fix at the PR for the duplicate ticket. This is why we should solve all problems twice, right?