Unsoundness in GADT pattern matching (falsely assumed injectivity)
See original GitHub issueCompiler 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:
- Created 2 years ago
- Reactions:1
- Comments:5 (4 by maintainers)
Top 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 >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
The wrong assumption is that:
In Scala >= 3.0.2 we at least get a warning (although a confusing one):