Skip to content

MirroredElemLabels of Mirror.Sum inferred to be EmptyTuple #14150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
johnhungerford opened this issue Dec 20, 2021 · 7 comments · Fixed by #15014
Closed

MirroredElemLabels of Mirror.Sum inferred to be EmptyTuple #14150

johnhungerford opened this issue Dec 20, 2021 · 7 comments · Fixed by #15014

Comments

@johnhungerford
Copy link

Compiler version

3.1.0

Minimized code

import scala.deriving.Mirror
import scala.util.NotGiven
import scala.compiletime.constValue

trait GetConstValue[T] {
    type Out
    def get : Out
}

object GetConstValue {
    type Aux[T, O] = GetConstValue[T] { type Out = O }

    inline given value[T <: Singleton](
        using
        ev : NotGiven[T <:< Tuple],
    ) : GetConstValue.Aux[T, T] = {
        val out = constValue[T]

        new GetConstValue[T] {
            type Out = T
            def get : Out = out
        }
    }

    given empty : GetConstValue[EmptyTuple] with {
        type Out = EmptyTuple
        def get : Out = EmptyTuple
    }

    given nonEmpty[H, HRes, Tail <: Tuple, TRes <: Tuple](
        using
        head : GetConstValue.Aux[H, HRes],
        tail : GetConstValue.Aux[Tail, TRes],
    ) : GetConstValue[H *: Tail] with {
        type Out = HRes *: TRes

        def get : Out = head.get *: tail.get
    }
}

trait MirrorNamesDeriver[T] {
    type Derived <: Tuple
    def derive : Derived
}

object MirrorNamesDeriver {
    given mirDeriver[T, ElemLabels <: NonEmptyTuple](
        using
        mir: Mirror.SumOf[T] { type MirroredElemLabels = ElemLabels },
        ev : GetConstValue.Aux[ElemLabels, ElemLabels],
    ): MirrorNamesDeriver[T] with {
        type Derived = ElemLabels

        def derive: ElemLabels = ev.get
    }

    def derive[T](using d : MirrorNamesDeriver[T]) : d.Derived = d.derive
}

sealed trait SuperT
final case class SubT1(int: Int) extends SuperT
final case class SubT2(str: String, dbl : Double, bool : Boolean) extends SuperT

// Works when type parameters are set explicitly
val successfulLabels = MirrorNamesDeriver.mirDeriver[SuperT, ("SubT1", "SubT2")].derive
println(successfulLabels)

// Fails when type parameters are inferred
val failedLabels = MirrorNamesDeriver.derive[SuperT]
println(failedLabels)

Output

(SubT1,SubT2)
(())

Expectation

(SubT1, SubT2)
(SubT1, SubT2)

Note that if you change mir: Mirror.SumOf[T] to mir: Mirror.ProductOf[T] in mirDeriver, and you change failedLabels to MirrorNamesDeriver.derive[SubT2], the output is:

(str,dbl,bool)

This means that for Mirror.Product, the compiler is able to infer the type parameter correctly but not for Mirror.Sum

@johnhungerford
Copy link
Author

I figured out a workaround here: https://stackoverflow.com/a/70430261/11329453

@bishabosha
Copy link
Member

bishabosha commented Dec 21, 2021

also another workaround with the same type arguments:

object MirrorNamesDeriver {

    given mirDeriver[T, ElemLabels <: NonEmptyTuple](
        using mir: Mirror.SumOf[T],
        ev : GetConstValue.Aux[mir.MirroredElemLabels, mir.MirroredElemLabels],
        ev1: ElemLabels =:= mir.MirroredElemLabels,
    ): MirrorNamesDeriver[T] with {
        type Derived = mir.MirroredElemLabels

        def derive: mir.MirroredElemLabels = ev.get
    }

    def derive[T](using d : MirrorNamesDeriver[T]) : d.Derived = d.derive
}

@johnhungerford
Copy link
Author

@bishabosha thanks that's much better -- didn't know you could cross-reference context parameters!

@bishabosha
Copy link
Member

bishabosha commented Apr 22, 2022

I'm not sure if there is a good way to solve this for the original code example, it would mean that the mirror synthesis would be able to instantiate the ElemLabels type parameter of the method - not sure this is something we want to do? @smarter

@smarter
Copy link
Member

smarter commented Apr 22, 2022

I'm not familiar with the mirror synthesis generation, does it synthesize a value of type Mirror.SumOf[T] { type MirroredElemLabels = ... }? If so I'd expect that to constrain ElemLabels to a specific type.

@bishabosha
Copy link
Member

bishabosha commented Apr 22, 2022

I'm not familiar with the mirror synthesis generation, does it synthesize a value of type Mirror.SumOf[T] { type MirroredElemLabels = ... }? If so I'd expect that to constrain ElemLabels to a specific type.

that is what is happening, just it will currently add an extra refinement for MirroredElemLabels to the generated mirror's type, rather than try to add constraints to any existing MirroredElemLabels refinement in the requested mirror type

@smarter
Copy link
Member

smarter commented Apr 22, 2022

The inferred type is:

              (
                deriving.Mirror.Sum{
                  MirroredType = SuperT; MirroredMonoType = SuperT;
                    MirroredElemTypes <: Tuple
                  ; MirroredElemLabels = (EmptyTuple.type *: EmptyTuple.type)
                }
               &
                scala.deriving.Mirror.Sum{
                  MirroredMonoType = SuperT; MirroredType = SuperT;
                    MirroredLabel = ("SuperT" : String)
                }
              ){
                MirroredElemTypes = (SubT1, SubT2);
                  MirroredElemLabels = (("SubT1" : String), ("SubT2" : String))
              }

This looks wrong to me, it seems that MirroredElemLabels is set both to (EmptyTuple.type *: EmptyTuple.type) and to (("SubT1" : String), ("SubT2" : String)), but it can't be both at the same time, so I think there's a code generation bug here (and in fact -Ycheck:typer explodes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants