Skip to content

summonInline in an inlined erasedValue match overwrites correct more specific type info #9110

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

Open
letalvoj opened this issue Jun 5, 2020 · 6 comments

Comments

@letalvoj
Copy link

letalvoj commented Jun 5, 2020

Minimized code

import scala.deriving._
import scala.compiletime._

enum E:
  case A

inline def headLabel[T<:NonEmptyTuple]:String = inline erasedValue[T] match
    case _: (t *: ts) =>
        val mp = summonInline[Mirror.ProductOf[t]]
        constValue[mp.MirroredLabel]

@main def psvm:Unit =
  val ms = summonInline[Mirror.SumOf[E]]

  // works when called directly on Tuple.Head, prints A 👍 
  val mp = summonInline[Mirror.ProductOf[Tuple.Head[ms.MirroredElemTypes]]]
  println(constValue[mp.MirroredLabel])
  
  // the same functionality wrapped to a function fails 💥
  println(headLabel[ms.MirroredElemTypes])  

https://scastie.scala-lang.org/letalvoj/pettT3IoQbGeYB83B4i8iA/12

Output

[error] 20 |  println(headLabel[ms.MirroredElemTypes])  
[error]    |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |          not a constant type: mp.MirroredLabel; cannot take constValue

Expectation

A
A

Relevant Type Tree

          {
            {
              val mp: deriving.Mirror.ProductOf[(E.A : E)] = 
                {
                  {
                    val t: 
                      
                        scala.deriving.Mirror.Singleton{
                          MirroredMonoType = (E.A : E); MirroredType = (E.A : E)
                            ; 
                          MirroredLabel = ("A" : String)
                        }
                      
                     = 
                      E$#A.$asInstanceOf$[
                        
                          scala.deriving.Mirror.Singleton{
                            MirroredMonoType = (E.A : E); 
                              MirroredType = (E.A : E)
                            ; MirroredLabel = ("A" : String)
                          }
                        
                      ]
                    t:
                      
                        scala.deriving.Mirror.Singleton{
                          MirroredMonoType = (E.A : E); MirroredType = (E.A : E)
                            ; 
                          MirroredLabel = ("A" : String)
                        }
                      
                  }
                }
              {
                ???.$asInstanceOf$[mp.MirroredLabel]:mp.MirroredLabel
              }:String
            }:String
          }

edit: Is it that the outer type mp: deriving.Mirror.ProductOf[(E.A : E)] erases the corect specific type of returned t?

@letalvoj letalvoj changed the title constValue of any nested type of Mirror inlined in an erasedValue match fails summonInline overwrites correct more specific type info Jun 5, 2020
@letalvoj letalvoj changed the title summonInline overwrites correct more specific type info summonInline in an inlined erasedValue match overwrites correct more specific type info Jun 5, 2020
@bishabosha
Copy link
Member

bishabosha commented Jun 5, 2020

This is a case of #8739, the type t does not have the same refinements as the type of the value returned from summonInline[t]. However, because it was called in an inline method then the refinements were not added to the type of value mp in headLabel.

If you put your logic within the RHS of summonFrom then the refinements are propagated:

enum E:
  case A

inline def headLabel[T<:NonEmptyTuple]:String = inline erasedValue[T] match
  case _: (t *: _) =>
    type MirroredProduct = Mirror.ProductOf[t]
    summonFrom {
      case mp: MirroredProduct =>
        constValue[mp.MirroredLabel]
    }

@main def psvm:Unit =
  val ms = summonInline[Mirror.SumOf[E]]
  println(headLabel[ms.MirroredElemTypes])

@bishabosha
Copy link
Member

reopen after offline discussion

@nicolasstucki
Copy link
Contributor

nicolasstucki commented Jun 8, 2020

mp should get the most precise type of the Mirror. This needs to change in the Inliner.

We do this for inline val but not other vals https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Inliner.scala#L1225-L1227

@letalvoj
Copy link
Author

letalvoj commented Jun 8, 2020

I was playing with this a bit more and the proposed fix almost works - the summoned mirror has a proper type

            val mirror =
                type MPt = Mirror.ProductOf[t]
                summonFrom{case m: MPt => m}
            val additional:Map[String,E] =
                if(constValue[Tuple.Size[mirror.MirroredElemLabels]] == 0)
                    val value = mirror.fromProduct(Product0).asInstanceOf[E]
                    Map(key -> value)
                else
                    Map.empty

yet the constValue can not provideTuple.Size[mirror.MirroredElemLabels]

[error]    |              val mirror: 
[error]    |                
[error]    |                  scala.deriving.Mirror.Product{
[error]    |                    MirroredType = 
[error]    |                      (in.vojt.loonyssh.CompressionAlgorithm.zlib : 
[error]    |                        in.vojt.loonyssh.CompressionAlgorithm
[error]    |                      )
[error]    |                    ; 
[error]    |                      MirroredMonoType = 
[error]    |                        (in.vojt.loonyssh.CompressionAlgorithm.zlib : 
[error]    |                          in.vojt.loonyssh.CompressionAlgorithm
[error]    |                        )
[error]    |                    ; MirroredElemTypes <: Tuple
[error]    |                  }
....
// yet the size check does not still work
....
[error]    |                (if 
[error]    |                  {
[error]    |                    (???.$asInstanceOf$[Tuple.Size[mirror.MirroredElemLabels]]:
[error]    |                      Tuple.Size[mirror.MirroredElemLabels]
[error]    |                    )
[error]    |                  }.==(0)

Is it because, according to the type tree, the mirror type seems to be erased partially? The

[error]    |                    MirroredType = 
[error]    |                      (in.vojt.loonyssh.CompressionAlgorithm.zlib : 
[error]    |                        in.vojt.loonyssh.CompressionAlgorithm
[error]    |                      )

is available, yet

[error]    |                    ; MirroredElemTypes <: Tuple

is kind of uinformative ^^

@nicolasstucki nicolasstucki removed their assignment Jan 13, 2021
@nicolasstucki nicolasstucki self-assigned this Jun 8, 2021
@nicolasstucki
Copy link
Contributor

Another alternative is to use macros

inline def headLabel[T <: NonEmptyTuple]: String =
  ${ impl[T] }


private def impl[T <: NonEmptyTuple: Type](using Quotes): Expr[String] = {
  Type.of[T] match
    case '[ head *: tail ] =>
      Expr.summon[Mirror.ProductOf[head]] match
        case Some('{ type label <: String; $_ : Mirror { type MirroredLabel = `label` } }) =>
          Expr(Type.valueOfConstant[label].get)
}

@nicolasstucki
Copy link
Contributor

To fix this we need to fix #8739

@nicolasstucki nicolasstucki removed their assignment Jun 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants