scalapattern-matchingmetaprogrammingcompiler-warningsscala-3

Misleading unreachable warning when a type pattern match that is the result of a inline expansion is involved?


The following method works fine when T and the types of all its components are specific types (contrary to abstract type parameters).

import scala.compiletime.*

private inline def loop[T <: Tuple, S <: Matchable](s: S, inline count: Int): Int = {
    inline erasedValue[T] match {
        case _: EmptyTuple.type => throw new MatchError(s)
        case _: (head *: tail) => s match {
            case _: head => count
            case _ => loop[tail, S](s, count + 1) // compiler warning points to the underscore here
        }
    }
}

/** Does the same that [[scala.deriving.Mirror.Sum.ordinal]] would if `T` was [[scala.deriving.Mirror.SumOf[A].MirroredElemTypes]]. */
inline def indexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[T <: scala.Tuple, A <: Matchable](a: A): Int = {
    loop[T, A](a, 0)
}

And the compiler warns with: Unreachable case except for null (if this is intentional, consider writing case null => instead) at

case _ => loop[tail, S](s, count + 1)

Which in my opinion is misleading because the message is correct only when the actual type of someone of the components of T at the call site is an abstract type parameter. If, on the contrary, the type of all components of T are specific then, when the inline method is expanded, the type represented by head would be specific and therefore, the warning is false in this case.

I mean, the compiler is showing the warning always, at the inline method's implementation, assuming the worst case: that someone of the types that compose T in the call site is an abstract type parameter (not specific).

It is a fact that the correctness of the code resulting from the expansion of an inline method may depend on the call site. At least when the inline method has other inline clauses. So, wouldn't be better if the warning was shown at the call site in order to avoid false warnings/errors on code that works fine in proper situations?


It is worth noting that a second warning is shown by the compiler telling that "the type test for A cannot be checked at runtime", but this second warning only appears after adding, somewhere else, a call that passes a T such that the type of a component is an abstract type parameter. For example, after adding the following code:

def func[A <: C, B <: C, C <: Matchable](c: C): Int = {
    indexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[(A, B), C](c)
}

This support the opinion that compiler messages which are evoked by inline code and that depend on the call site should be shown at the call site in order to allow correct code that otherwise would be flagged with warnings/errors. Or, at least, such errors/warnings should be different from the regular ones in order to allow its suppression without the fear of silencing important ones.

And the question is, is that the intended behavior of the compiler, or should be reported as a bug?

Compiler version 3.4.1


Solution

  • The following method works fine when T and the types of all its components are specific types (contrary to abstract type parameters).

    The method doesn't seem to work for T = (Int, String, Boolean) and S = Int or Boolean

    https://scastie.scala-lang.org/DmytroMitin/zxENZdYnSQW0gcvH5Xmqhw

    https://scastie.scala-lang.org/DmytroMitin/zxENZdYnSQW0gcvH5Xmqhw/1

    indexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[(Int, String, Boolean), Int](1)
    //compile-time error: cannot test if value of type Int is a reference of class String
    
    indexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[(Int, String, Boolean), String]("a") 
    // compile-time warning: Unreachable case except for null (if this is intentional, consider writing case null => instead).
    // runtime: 1
    
    indexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[(Int, String, Boolean), Boolean](true) 
    //compile-time error: cannot test if value of type Boolean is a reference of class String
    

    The pattern matching doesn't seem safe.

    Anyway if you're sure that you apply your method only when everything is ok you can switch the warning off with @unchecked

    case _: (head *: tail) => (s: @unchecked) match {
    ...
    

    Why not to implement the logic on type level?

    import scala.compiletime.ops.int.+
    
    type IndexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[T <: Tuple, A] <: Int =
      T match
        case h *: t => A match
          case `h` => 0
          case _ => 1 + IndexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[t, A]
    
    inline def indexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[T <: Tuple, A](
      a: A
    ): IndexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[T, A] =
      constValue[IndexOfFirstComponentTypeOfTupleToWhichTheArgumentConformsTo[T, A]]