Cannot override a type with non-volatile upper bound

人盡茶涼 提交于 2019-12-03 07:25:23

Removing this check in the compiler lets us shine a light on the potential for unsoundness.

diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 37a7e3c..78a8959 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -5128,8 +5128,7 @@ trait Typers extends Adaptations with Tags {

       def typedSelectFromTypeTree(tree: SelectFromTypeTree) = {
         val qual1 = typedType(tree.qualifier, mode)
-        if (qual1.tpe.isVolatile) TypeSelectionFromVolatileTypeError(tree, qual1)
-        else typedSelect(tree, qual1, tree.name)
+        typedSelect(tree, qual1, tree.name)
       }

       def typedTypeBoundsTree(tree: TypeBoundsTree) = {

Then, running the code from a compiler test case for illegal type selection for volatile types:

scala> class A; class B extends A
defined class A
defined class B

scala> trait C {
     |   type U
     |   trait D { type T >: B <: A }
     |   val y: (D with U)#T = new B
     | }
defined trait C

scala> class D extends C {
     |   trait E
     |   trait F { type T = E }
     |   type U = F
     |   def frob(arg : E) : E = arg
     |   frob(y)
     | }
defined class D

scala> new D
java.lang.ClassCastException: B cannot be cast to D$E

As I understand it, the issue stems from the fact that Scala doesn't have true intersection types.

scala> type A = { type T = Int }
defined type alias A

scala> type B = { type T = String }
defined type alias B

scala> "": (A with B)#T
res16: String = ""

scala> 0: (A with B)#T
<console>:37: error: type mismatch;
 found   : Int(0)
 required: String
              0: (A with B)#T
              ^

This might change in the future, if the research into Dependent Object Types (DOT) bears fruit.

You can rewrite trait B with (more later about your goal, which, I think, is a bit different)

trait B extends A {
  type MyType <: BInner with AInner
}

And this makes total sense. A value of type B#MyType can be seen as either a BInner or a AInner.

You don't need to repeat Abstract because A is already a subclass of Abstract. You don't have to write override as this is implicit for a type declaration. So the question is why A#MyType is not working as AInner?

Here is what the scala language spec says about volatile types.

3.6 Volatile Types

Type volatility approximates the possibility that a type parameter or abstract type instance of a type does not have any non-null values. As explained in (§3.1), a value member of a volatile type cannot appear in a path. A type is volatile if it falls into one of four categories: A compound type T1 with ... with Tn {R } is volatile if one of the following two conditions hold. 1. One of T2, ..., Tn is a type parameter or abstract type, or 2. T1 is an abstract type and and either the refinement R or a type Tj for j > 1 contributes an abstract member to the compound type, or 3. one of T1, ..., Tn is a singleton type. Here, a type S contributes an abstract member to a type T if S contains an abstract member that is also a member of T . A refinement R contributes an abstract member to a type T if R contains an abstract declaration which is also a member of T . A type designator is volatile if it is an alias of a volatile type, or if it designates a type parameter or abstract type that has a volatile type as its upper bound. A singleton type p.type is volatile, if the underlying type of path p is volatile. An existential type T forSome {Q } is volatile if T is volatile.

Other important item mentioned by the spec is about abstract type overriding:

Another restriction applies to abstract type members: An abstract type member with a volatile type (§3.6) as its upper bound may not override an abstract type member which does not have a volatile upper bound.

The compiler error is:

error: overriding type MyType in trait A with bounds <: AInner;
type MyType is a volatile type; cannot override a type with non-volatile upper bound

This is consistent with the spec. BInner with A#MyType is volatile. Before that MyType had a non-volatile as Any.

The matter is that a type in the scala type system must have a unique meaning. An abstract type can be thought as a type which declaration is deferred to a subclass. Therefore there is no problem for declaring values of an abstract type when it is still abstract. On the other hand if we have a type like BInner with A#MyType, this type may have several meaning. It is called volatile and it does not makes sense to have a non null value of this type, as it could have as many types as subclasses instantiating the MyType abstract type. To simplify things, we could think of a volatile type as a type not being a subtype of Any (and volatile as being a subtype Any). We therefore have a contradiction that the compiler mentions.

Coming back to your goal, which you stated as

What I'm trying to achieve here(in trait B) is to further restrict the type MyType declared > in Abstract, so any value of type MyType must extend all the MyTypes in the mixin tree.

You can achieve this thanks to inner traits like this.

trait Abstract {
  type MyType
}
trait B extends Abstract {
  trait MyType {
    def bMethod : Int
  }
}
trait A extends B {
  trait MyType extends super.MyType {
  }
}

Well I hope this somewhat what you're looking for.

What is wrong with this?

trait B extends Abstract with A {
  override type MyType <: BInner with AInner
}

In any realization of trait B, MyType will always be the same type as seen from trait A, so upper-bounding it by itself does not make any sense.

If in the upper piece of code, it bothers you that you'll have to rewrite trait B if you change the bound in trait A, use:

trait A extends Abstract{
  type ABound = AInner
  type MyType <: AInner
}
trait B extends Abstract with A {
  override type MyType <: BInner with ABound
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!