bug
bug copied to clipboard
Existential type not stable
When having an existentially parameterized value val foo: Foo[_], I would expect the type argument, although unknown, be stable among all usages of foo. This is clearly not the case:
class Foo[A]
object Main {
val foo: Foo[_] = new Foo[String]
// Test 1
def f[A](foo1: Foo[A], foo2: Foo[A]): Unit = ()
f(foo, foo) // error
// Test 2
case class Bar[A](foo: Foo[A]) {
def f(foo: Foo[A]): Unit = ()
}
Bar(foo).f(foo) // error
}
This is the compiler output:
$ ~/scala-2.12.2/bin/scalac main.scala
main.scala:8: error: type mismatch;
found : Foo[_$1] where type _$1
required: Foo[Any]
Note: _$1 <: Any, but class Foo is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
f(foo, foo) // error
^
main.scala:8: error: type mismatch;
found : Foo[_$1] where type _$1
required: Foo[Any]
Note: _$1 <: Any, but class Foo is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
f(foo, foo) // error
^
main.scala:14: error: type mismatch;
found : Foo[(some other)_$1(in value <local Main>)] where type (some other)_$1(in value <local Main>)
required: Foo[_$1(in value <local Main>)] where type _$1(in value <local Main>)
Bar(foo).f(foo) // error
^
three errors found
foo being effectively final is essential to your example. Anyhow, just as an FYI, it is possible to capture the skolem in a fairly general way,
scala> def withFoo[T, U](foo: Foo[T])(f: Foo[T] => U): U = f(foo)
withFoo: [T, U](foo: Foo[T])(f: Foo[T] => U)U
scala> withFoo(foo)(fooT => f(fooT, fooT))
scala> withFoo(foo)(fooT => Bar(fooT).f(fooT))
Your suggestion amounts to saying that this transformation should be automatic where the existential value is effectively final.
Thanks for the withFoo suggestion, @milessabin.
Just to clarify, by "effectively final", do you mean what scalac calls a "stable identifier", as in this error message?
scala> class Foo { type A }
defined class Foo
scala> def foo: Foo = new Foo
foo: Foo
scala> List[foo.A]()
<console>:13: error: stable identifier required, but foo found.
List[foo.A]()
Or you can use a simple match instead of withFoo:
class Foo[A]
object Main {
val foo: Foo[_] = new Foo[String]
// Test 1
def f[A](foo1: Foo[A], foo2: Foo[A]): Unit = ()
foo match { case foo1 => f(foo1, foo1) } // No error
// Test 2
case class Bar[A](foo: Foo[A]) {
def f(foo: Foo[A]): Unit = ()
}
foo match { case foo1 => Bar(foo1).f(foo1) } // No error
}