question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unsoundness in GADT pattern matching (falsely assumed injectivity)

See original GitHub issue

Compiler version

3.0.1-RC2

Minimized code

class Functions[*[_, _]] {

  sealed trait Fun[A, B] {
    def ev: A =:= B
  }

  case class Id[X]() extends Fun[X, X] {
    override def ev: X =:= X =
      implicitly[X =:= X]
  }

  case class Par[A1, A2, B1, B2](
    f1: Fun[A1, B1],
    f2: Fun[A2, B2],
  ) extends Fun[A1 * A2, B1 * B2] {
    override def ev: (A1 * A2) =:= (B1 * B2) =
      f2.ev.substituteCo[[x] =>> (A1 * A2) =:= (B1 * x)](
        f1.ev.substituteCo[[x] =>> (A1 * A2) =:= (x * A2)](
          implicitly[(A1 * A2) =:= (A1 * A2)]
        )
      )
  }

  def prove[A1, A2, B1, B2](
    f: Fun[A1 * A2, B1 * B2]
  ): Option[(A1 =:= B1, A2 =:= B2)] =
    f match {
      case Par(f1, f2) => Some((f1.ev, f2.ev)) // falsely assumed injectivity of *[_, _]
      case Id()        => None
    }
}

def main(args: Array[String]): Unit = {
  type *[A, B] = Unit

  val functions = new Functions[*]
  import functions._

  val f: Fun[String * Int, Long * Char] =
    Par(Id[String](), Id[Int]())

  prove[String, Int, Long, Char](f) map {
    case (string_eq_long, _) => string_eq_long("crash")
  }
}

Output

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Long (java.lang.String and java.lang.Long are in module java.base of loader 'bootstrap')
  at scala.runtime.BoxesRunTime.unboxToLong(BoxesRunTime.java:103)
  at repl$.rs$line$1$.main$$anonfun$1(rs$line$1:43)
  at scala.Option.map(Option.scala:242)
  at repl$.rs$line$1$.main(rs$line$1:44)
  ... 28 elided

Expectation

Soundness problem is on the commented line in the source. Strictly speaking, it should not compile. However, I expect most type constructors to be injective and it is useful to be able to use that fact in pattern matching. So I would like this to be fixed (by rejecting the program at compile time) only after there is a way to tell the compiler that an abstract type constructor is injective.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
joroKr21commented, Jul 15, 2021

The wrong assumption is that:

f: Fun[A1 * A2, B1 * B2]
f: Par[?A, ?B, ?C, ?D] <:< Fun[?A * ?B, ?C * ?D]
// implies (true, Fun is a trait => injective)
(?A * ?B) =:= (A1 * A2)
(?C * ?D) =:= (B1 * B2)
// implies (wrong, * is abstract => injectivity unknown)
?A =:= A1
?B =:= A2
?C =:= B1
?D =:= B2
0reactions
smartercommented, Mar 24, 2022

In Scala >= 3.0.2 we at least get a warning (although a confusing one):

-- [E029] Pattern Match Exhaustivity Warning: try/i13080.scala:27:4 ------------
27 |    f match {
   |    ^
   |match may not be exhaustive.
   |
   |It would fail on pattern case: Functions[?].Id(), Functions[?].Par(_, _)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Another GADT typechecking error · Issue #13074 - GitHub
this example does not need injectivity;; injectivity already seems to be falsely assumed elsewhere anyway, see Unsoundness in GADT pattern ...
Read more >
How to prove (a * b)%type = (c * d)%type - Coq's discourse
It is not actually obvious that type constructors ought to be injective, and univalence is a prime example of a reasoning principle that...
Read more >
Mutable matching - Counterexamples in Type Systems
This assumption can be violated by the presence of two features: first, the ability to pattern-match on mutable fields, and second, the ability...
Read more >
Implementing Path-Dependent GADT Reasoning for Scala 3
This extra type information is then retrieved during pattern matching, which requires special reasoning capabilities from the type checker.
Read more >
A Translation of OCaml GADTs into Coq
GADTs while also allowing pattern matching with impos- sible branches to be translated ... no better off than before, as it is impossible...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found