Solved: Scala 3 inline fails due to type marching using Tuple.Fold

I am looking through this article. They have the following code:

inline def labels[Labels <: Tuple](using Tuple.Union[Labels] <:< String):  List[String] =
  inline erasedValue[Labels] match {
    case _: EmptyTuple => 
      List.empty
    case _: (head *: tail) => 
      constValue[head].asInstanceOf[String] :: labels[tail]
  }

This fails with the following error:

[error] 108 |        constValue[head].asInstanceOf[String] :: labels2[tail]
[error]     |                                                              ^
[error]     | Cannot prove that Tuple.Union[tail]
[error]     |
[error]     | where:    tail is a type in method labels2 with bounds <: Tuple
[error]     |  <:< String..
[error]     | I found:
[error]     |
[error]     |     t
[error]     |
[error]     | But parameter t does not match type Tuple.Union[tail] <:< String.
[error]     |
[error]     | Note: a match type could not be fully reduced:
[error]     |
[error]     |   trying to reduce  Tuple.Union[tail]
[error]     |   trying to reduce  scala.Tuple.Fold[tail, Nothing, [x, y] =>> x | y]
[error]     |   failed since selector  tail
[error]     |   does not match  case EmptyTuple => Nothing
[error]     |   and cannot be shown to be disjoint from it either.
[error]     |   Therefore, reduction cannot advance to the remaining case
[error]     |
[error]     |     case h *: t => h | scala.Tuple.Fold[t, Nothing, [x, y] =>> x | y]

The code looks correct so I created this code online (slight change to using, named it t):

and tried several versions of Scala 3.x.y. They consistently give the same error. Intuitively I would say that if a tuple has all member types T, then any subset of that tuple will also have that same type T. The compiler does not seem to infer this.

So my questions are:

  1. Am I thinking correctly?
  2. What can I do to let the compiler infer the desired type?

TIA,
HF

1 Like

Finally found this solution:

inline def labels2[Labels <: Tuple](using t: Tuple.Union[Labels] <:< String):  List[String] =
  inline erasedValue[Labels] match {
    case _: EmptyTuple => 
      List.empty[String]
    case _: (head *: tail) => 
      constValue[head].asInstanceOf[String] :: labels2[tail](using summonInline[Tuple.Union[tail] <:< String])
  }  

Seems to be logical, but their may be a better solution. If you have one, please tell me. Scastie link above has been updated.

Thanks

There is a very short discussion about this topic here.

1 Like