scala/scala3

Regression in shapless2 scala 3 port due to new specced match types

Closed this issue · 3 comments

Compiler version

3.4.2 bisected to 5260c6023fb24d0362e55c5fedc320b562cb553f

Minimized code

The code was minimized from the hlist implementation in the Scala 3 port of shapeless 2. https://github.com/milessabin/shapeless/blob/832308b4457b62acfdbeae13fa00197cd0ab6ae1/core/src/main/scala-3/shapeless/hlists.scala#L11-L13

import scala.Tuple.Head

object Head {
  def head[T <: NonEmptyTuple](tuple: T): Head[T] = tuple match {
    case cons: *:[h, t] => cons.head
  }
}

Output

-- [E007] Type Mismatch Error: tuple_head.scala:5:27 ------------------------------------------------------------------
5 |    case cons: *:[h, t] => cons.head
  |                           ^^^^^^^^^
  |                       Found:    Tuple.Head[T & h *: t]
  |                       Required: h
  |
  |                       where:    T is a type in method head with bounds <: NonEmptyTuple
  |                                 h is a type in method head
  |                                 t is a type in method head with bounds <: Tuple
  |
  |
  |                       Note: a match type could not be fully reduced:
  |
  |                         trying to reduce  Tuple.Head[T & h *: t]
  |                         failed since selector T & h *: t
  |                         does not uniquely determine parameter x in
  |                           case x *: _ => x
  |                         The computed bounds for the parameter are:
  |                           x <: h
  |                       Note that implicit conversions were not tried because the result of an implicit conversion
  |                       must be more specific than h
  |
  | longer explanation available when compiling with `-explain`
1 error found

Expectation

The code compiles without errors as it does in versions prior to 3.4.0.

sjrd commented

This specific issue is not so much about the new match types. It fixed a soundness hole. We cannot make it compiler without reintroducing the unsoundness.

Fixing soundness bugs is not considered a regression.

Is there some other way the code can be written to not hit this? Because I thought these particular examples were sound.

So I figured out that one can make it compile by introducing an itermediate variable and discarding some of the type information. e.g

import scala.Tuple.Head

object Head {
  def head[T <: NonEmptyTuple](tuple: T): Head[T] = tuple match {
    case cons: *:[h, t] =>
      val tmp = cons: *:[h, t]
      tmp.head
  }
}