Skip to content
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

Existentials failing to typecheck when double-bounded #12772

Open
s5bug opened this issue Apr 9, 2023 · 0 comments
Open

Existentials failing to typecheck when double-bounded #12772

s5bug opened this issue Apr 9, 2023 · 0 comments
Labels
Milestone

Comments

@s5bug
Copy link

s5bug commented Apr 9, 2023

Reproduction steps

Scala version: 2.13.10

@ def foo1(b: (T forSome { type T })): Box[T forSome { type T }] = new Box(b)
defined function foo1

@ def foo2[B](b: (T forSome { type T ; type X <: B })): Box[T forSome { type T ; type X <: B }] = new Box(b)
defined function foo2

@ def foo3[B](b: (T forSome { type T ; type X >: Box[T] <: B })): Box[T forSome { type T ; type X >: Box[T] <: B }] = new Box(b)
cmd23.sc:1: type mismatch;
 found   : (some other)T(in method foo3) where type (some other)T(in method foo3)
 required: T(in method foo) forSome { type T(in method foo3); type X >: ammonite.$sess.cmd11.Box[T(in method foo3)] <: B }
def foo3[B](b: (T forSome { type T ; type X >: Box[T] <: B })): Box[T forSome { type T ; type X >: Box[T] <: B }] = new Box(b)

    ^
Compilation Failed

@ def foo4(b: (T forSome { type T ; type X >: Box[T] })): Box[T forSome { type T ; type X >: Box[T] }] = new Box(b)
defined function foo4

@ def foo5(b: (T forSome { type T ; type X >: Box[T] })): (X forSome { type T ; type X >: Box[T] }) = {
>   val boxed: Box[T forSome { type T ; type X >: Box[T] }] = new Box(b)
>   val asX: (X forSome { type T ; type X >: Box[T] }) = boxed
>   asX
> }
defined function foo5

Problem

I expect that, because foo2 and foo4 compile (adding a lower bound or adding an upper bound to an existential doesn't interfere with typechecking) foo3 should also compile (if a lower bound is valid and an upper bound is valid, the same program should still be valid).

Note that T forSome { type T ; type X >: Box[T] <: B } is valid for typechecking. I wrote it trying to solve a problem posed by @lihaoyi:

class Box[T](t: T)
class Call[B <: Box[_]](ub: ???) { def value: B = Box(ub) }

object Foo extends Call[Box[Int]](1) // How to make this typecheck?
object Bar extends Call[Box[Int]]("not an int") // And this fail?

My idea was to use an existential to posit some type T such that Box[T] <: B. I know that one can replace evidence like implicit ev: A <:< B with a type parameter [X >: A <: B], making the method only callable if A <: B, and I figured this technique can be applied to existentials as well. The following typechecks:

class Box[T](t: T)
class Call[B <: Box[_]](ub: T forSome { type T ; type X >: Box[T] <: B })

object Foo extends Call[Box[Int]](1)

and the following fail:

val x: Box[Int] = Box(1)
object X extends Call[x.type](1)

object Y extends Call[Box[Int]]("not an int")

The problem is that one cannot implement def value: B with this signature, due to the unique double-bounded existential failure:

@ class Call[B <: Box[_]](b: (T forSome { type T ; type X >: Box[T] <: B })) {
>   def value: B = {
>     val original: Box[T forSome { type T ; type X >: Box[T] <: B }] = new Box[T forSome { type T ; type X >: Box[T] <: B }]
>     val simplifyToX: (X forSome { type T ; type X >: Box[T] <: B }) = original
>     val simplifyToB: B = simplifyToX
>     simplifyToB
>   }
> }
cmd1.sc:3: type mismatch;
 found   : (some other)T(in value original) where type (some other)T(in value original)
 required: T(in value original) forSome { type T(in value original); type X >: ammonite.$sess.cmd0.Box[T(in value original)] <: B }
    val original: Box[T forSome { type T ; type X >: Box[T] <: B }] = new Box[T forSome { type T ; type X >: Box[T] <: B }](b)

    ^
cmd1.sc:3: type mismatch;
 found   : ammonite.$sess.cmd0.Box[(some other)T(in value original) forSome { type (some other)T(in value original); type X >: ammonite.$sess.cmd0.Box[(some other)T(in value original)] <: B }]
 required: ammonite.$sess.cmd0.Box[T(in value original) forSome { type T(in value original); type X >: ammonite.$sess.cmd0.Box[T(in value original)] <: B }]

    val original: Box[T forSome { type T ; type X >: Box[T] <: B }] = new Box[T forSome { type T ; type X >: Box[T] <: B }](b)
                                                                      ^
cmd1.sc:4: type mismatch;
 found   : ammonite.$sess.cmd0.Box[T(in value original) forSome { type T(in value original); type X >: ammonite.$sess.cmd0.Box[T(in value original)] <: B }]
 required: X forSome { type T(in value simplifyToX); type X >: ammonite.$sess.cmd0.Box[T(in value simplifyToX)] <: B }
    val simplifyToX: (X forSome { type T ; type X >: Box[T] <: B }) = original
                                                                      ^
Compilation Failed

Yet foo5 shows that the original and simplifyToX steps should work, and (X forSome { type X <: U }) means that simplifyToB should work (as it seems to in this example).

@SethTisue SethTisue added the typer label Aug 8, 2023
@SethTisue SethTisue added this to the Backlog milestone Aug 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants