-
Notifications
You must be signed in to change notification settings - Fork 1.1k
An opaque type should not be covariant when the underlying type is invariant #13997
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
Comments
Looks like a soundness issue. For non-primitives, it throws case class Invariant[A](var a: A)
opaque type CovariantArray[+A] = Invariant[A]
object CovariantArray:
def crash() =
val stringArray: CovariantArray[String] = Invariant("foo")
val anyArray: CovariantArray[Any] = stringArray
anyArray.a = 42
stringArray.a.length
@main
def main =
CovariantArray.crash() Stacktrace:
|
The variance checker on top-level types should be called from https://github.com/lampepfl/dotty/blob/51828cc215ac2b181b6a978144e33e971ba01589/compiler/src/dotty/tools/dotc/transform/PostTyper.scala#L380, I don't know if it's not called at all here or if the check is incomplete. |
Actually I would say we could still preserve soundness if only the compiler was able to prove that if we define an opaque type with covariant or contravariant type parameters we don't use them in contravariant and covariant manners respectively. E.g. import scala.reflect.ClassTag
trait Polygon(val edgesCount: Int)
class Triangle extends Polygon(3)
class Square extends Polygon(4)
object Opaque:
opaque type CovariantArray[+A] = Array[A]
object CovariantArray:
def apply[A : ClassTag](elems: A*): CovariantArray[A] = Array(elems*)
extension [A : ClassTag](arr: CovariantArray[A])
def ++[B : ClassTag](arr2: CovariantArray[B])(using ClassTag[A | B]): CovariantArray[A | B] =
val elems: Seq[A | B] = arr.toSeq ++ arr2.toSeq
elems.toArray
def elems: Seq[A] = arr.toSeq
import Opaque.CovariantArray
def countEgdes(polygons: CovariantArray[Polygon]) = polygons.elems.map(_.edgesCount).sum[Int]
@main def run() =
val triangles: CovariantArray[Triangle] = CovariantArray(new Triangle)
val squares: CovariantArray[Square] = CovariantArray(new Square)
val polygons: CovariantArray[Polygon] = triangles ++ squares
println(countEgdes(triangles))
println(countEgdes(polygons)) Not sure how useful this would be but we can implement a covariant/immutable data structure backed by an invariant/mutable one |
We can already achieve something like this using wildcards: https://github.com/lampepfl/dotty/blob/51828cc215ac2b181b6a978144e33e971ba01589/library/src/scala/IArray.scala#L8, more complicated checks don't seem worth it (and would be very hard to implement correctly I think). |
The trick with a wildcard will indeed prevent you from inserting an opaque type CovariantArray[+A] = Array[_ <: A]
object CovariantArray:
def test() =
val stringArray: CovariantArray[String] = Array("foo", "bar")
stringArray(0) = "baz"
@main def run() =
CovariantArray.test() [error] Found: ("baz" : String)
[error] Required: stringArray.T
[error] stringArray(0) = "baz"
[error] ^^^^^ |
In any case you can always use |
@smarter where should I put the annotation? I tried putting it in different places to make my example work but the code still doesn't compile. |
type CovariantArray[+A] = Array[A @scala.annotation.unchecked.uncheckedVariance] |
OK I thought one would need to put the annotation on the wildcard (which doesn't seem to be possible BTW - is this a bug?) |
I don't understand what you mean, which wildcard would you put an annotation on? But this discussion is now completely unrelated to the original bug. |
I thought of something like type CovariantArray[+A] = Array[_ @scala.annotation.unchecked.uncheckedVariance <: A] |
I don't see a bug, putting it after ( |
Compiler version
3.1.0
Minimized code
Output
Expectation
Should not compile
The text was updated successfully, but these errors were encountered: