Skip to content

Unsoundness due to projections from structural types #4098

Closed
@Blaisorblade

Description

@Blaisorblade

A new variant of #50 (re)surfacing, haven't checked yet among all the original examples, nor if this is fixed by the existing PRs (in particular, #4036). The issue is that (({ type R = A }) & ({ type R = B }))#R is allowed and shouldn't, so I adapted @alexknvl's code in #4060 to implement coerce.

object App {
  def coerce[U, V](u: U): V = {
    type X = { type R >: U }
    type Y = { type R = V }
    type Z = X & Y
    val u1: Z#R = u

    u1
  }

  def main(args: Array[String]): Unit = {
    val x: Int = coerce[String, Int]("a")
    println(x + 1)
  }
}
$ dotc -d out UnsoundProj.scala
$ dotr -classpath out App
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
        at App$.main(UnsoundProj.scala:11)
        at App.main(UnsoundProj.scala)

This was prompted by a question from @allanrenucci. He also pointed me to the documentation on type projections (http://dotty.epfl.ch/docs/reference/dropped/type-projection.html), which seems both unclear and misleading:

Scala so far allowed general type projection T#A where T is an arbitrary type and A names a type member of T.
Dotty disallows this if T is an abstract type (class types and type aliases are fine). This change was made because unrestricted type projection is unsound.

Projections should be illegal on lots of types T that aren't abstract types themselves — T must be a class type (or alias to such), and A should be a fully defined type member, but type operators like intersections, unions, structural refinements should be forbidden — probably all operators should (unless we have a very good reason for allowing some).

This way, type projections can only access global (type) constants:

class Defining {
  type TypeMember = ...
}
object Defining {
  val valueMember
}
// Here, Defining#T and Defining.valueMember are both references to known global constants, at value or type-level

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions