-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type matches are unsound. #6314
Comments
In fact, you do not need the new opaque types: object G {
final class X
final class Y
trait FooSig {
type Type
def apply[F[_]](fa: F[X & Y]): F[Y & Type]
}
val Foo: FooSig = new FooSig {
type Type = X & Y
def apply[F[_]](fa: F[X & Y]): F[Y & Type] = fa
}
type Foo = Foo.Type
type Bar[A] = A match {
case X & Y => String
case Y => Int
}
def main(args: Array[String]): Unit = {
val a: Bar[X & Y] = "hello"
val i: Bar[Y & Foo] = Foo.apply[Bar](a)
val b: Int = i
println(b + 1)
}
} |
Here is a cleaner example: object G {
final class X
final class Y
opaque type Foo = Nothing // or X & Y
object Foo {
def apply[F[_]](fa: F[X & Foo]): F[Y & Foo] = fa
}
type Bar[A] = A match {
case X => String
case Y => Int
}
val a: Bar[X & Foo] = "hello"
val b: Bar[Y & Foo] = 1
def main(args: Array[String]): Unit = {
val a: Bar[X & Foo] = "hello"
val i: Bar[Y & Foo] = Foo.apply[Bar](a)
val b: Int = i
println(b + 1)
}
} EDIT: this example still compiles with the fix in dotty-staging@74168ac |
Thanks for the bug reports, it's very helpful! That's clearly a bug, |
I don't think it's just intersections, object G {
trait Wizzle[L <: Int with Singleton] {
type Bar[A] = A match {
case 0 => String
case L => Int
}
def left(fa: String): Bar[0] = fa
def right(fa: Bar[L]): Int = fa
def center[F[_]](fa: F[0]): F[L]
def run: String => Int = left andThen center[Bar] andThen right
}
class Wozzle extends Wizzle[0] {
def center[F[_]](fa: F[0]): F[0] = fa
}
def main(args: Array[String]): Unit = {
val coerce: String => Int = (new Wozzle).run
println(coerce("hello") + 1)
}
} Singletons are suspect too object G {
trait Wizzle {
type X <: Int with Singleton
type Y <: Int with Singleton
type Bar[A] = A match {
case X => String
case Y => Int
}
def left(fa: String): Bar[X] = fa
def center[F[_]](fa: F[X]): F[Y]
def right(fa: Bar[Y]): Int = fa
def run: String => Int = left andThen center[Bar] andThen right
}
class Wozzle extends Wizzle {
type X = 0
type Y = 0
def center[F[_]](fa: F[X]): F[Y] = fa
}
def main(args: Array[String]): Unit = {
val coerce: String => Int = (new Wozzle).run
println(coerce("hello") + 1)
}
} |
Conceptually,
All of the examples above attack compiler's over-eagerness to reduce type Foo
type Bar[A] = A match {
case X => String
case Y => Int
}
type X <: Int with Singleton
type Y <: Int with Singleton
type Bar[A] = A match {
case X => String
case Y => Int
}
|
Makes perfect sense, this is exactly what the implementation is supposed to do: never "reduce past" a case unless there is proof that the two types (the scrutinee type and the pattern type) are non intersecting. |
After snooping around the code for a bit, I found another example: object G {
type Void <: Nothing
trait Wizzle {
type Razzle[+X >: Void]
type X = 0
type Y = 1
type Bar[A] = A match {
case Razzle[X] => String
case Razzle[Y] => Int
}
def left(fa: String): Bar[Razzle[X]] = fa
def center[F[_]](fa: F[Razzle[X]]): F[Razzle[Y]]
def right(fa: Bar[Razzle[Y]]): Int = fa
def run: String => Int = left andThen center[Bar] andThen right
}
class Wozzle extends Wizzle {
type Razzle[+X >: Void] = Int
def center[F[_]](fa: F[Razzle[X]]): F[Razzle[Y]] = fa
}
def main(args: Array[String]): Unit = {
val coerce: String => Int = (new Wozzle).run
println(coerce("hello") + 1)
}
} This one attacks line https://github.com/dotty-staging/dotty/blob/74168ac13746ef43cc3f626cf183b9d452bdc4da/compiler/src/dotty/tools/dotc/core/TypeComparer.scala#L1959 and the surrounding case for |
I think the code might be mixing the inhabitance of intersections ( final class A
final class B
type Select[X] = X match {
case A => Int
case B => String
}
type Type
val a : Select[A & Type] = 1
val b : Select[B & Type] = "hey" attacks https://github.com/dotty-staging/dotty/blob/74168ac13746ef43cc3f626cf183b9d452bdc4da/compiler/src/dotty/tools/dotc/core/TypeComparer.scala#L1931 - EDIT: clarified the first statement |
You nailed it with this last example. I'm now convinced that there is no way to soundly reduce any match types when abstract type are involved. |
There are some cases where it is possible. final case class X()
type Y
type Select[X] = X match {
case List[X] => Int
case Option[Y] => String
} final class X
final class Y
type Select[X] = X match {
case X => Int
case Y => String
}
type Type <: X
type Foo = Select[X | Type] // can not be a subtype of Y, subtype of X, hence reduces to Int final class X
final class Y
type Select[X] = X match {
case X => Int
case Y => String
}
type Type <: X
type Foo = Select[X & Type] // can not be a subtype of Y, subtype of X, hence reduces to Int If the compiler could use implicit evidence (probably not any time soon): final class X
final class Y
type Select[X] = X match {
case X => Int
case Y => String
}
type Type
implicit val p: (Type <:< X) => Nothing // a proof that Type is not a subtype of X
type Foo = Select[Y & Type] // safe to reduce to String But otherwise yes, things get really tricky when you are dealing with abstract types in type matches. |
The text was updated successfully, but these errors were encountered: