当包对象扩展trait时,WeakTypeTag不起作用



考虑这个带有WeakTypeTag的人为示例:

import reflect.runtime.universe.WeakTypeTag
import reflect.runtime.universe.weakTypeOf
package object stackoverflow1 {
  sealed trait Status
  trait Open extends Status
  trait Closed extends Status
  case class Buffer[S <: Status](available: Int)
  object Buffer {
    def create(available: Int): Buffer[Open] = new Buffer[Open](available)
    def from[S <: Status: WeakTypeTag](b: Buffer[S]): Buffer[Open] = {
      weakTypeOf[S] match {
        case t if t <:< weakTypeOf[Open]   => b.asInstanceOf[Buffer[Open]]
        case t if t <:< weakTypeOf[Closed] => new Buffer[Open](b.available)
      }
    }
  }
}
package stackoverflow1 {
  object Main extends App {
    val b1 = Buffer.create(1024)
    val b2 = Buffer.from(b1)
  }
}

这很好,但当我将包对象的代码移动到一个特性中并将该特性混合到包对象中时,它就不再起作用了:

import reflect.runtime.universe.WeakTypeTag
import reflect.runtime.universe.weakTypeOf
package stackoverflow2 {
  trait Mixin {
    sealed trait Status
    trait Open extends Status
    trait Closed extends Status
    case class Buffer[S <: Status](available: Int)
    object Buffer {
      def create(available: Int): Buffer[Open] = new Buffer[Open](available)
      def from[S <: Status: WeakTypeTag](b: Buffer[S]): Buffer[Open] = {
        weakTypeOf[S] match {
          case t if t <:< weakTypeOf[Open]   => b.asInstanceOf[Buffer[Open]]
          case t if t <:< weakTypeOf[Closed] => new Buffer[Open](b.available)
        }
      }
    }
  }
}
package object stackoverflow2 extends Mixin
package stackoverflow2 {
  object Main extends App {
    val b1 = Buffer.create(1024)
    val b2 = Buffer.from(b1)
  }
}

错误消息:
Exception in thread "main" scala.MatchError: stackoverflow2.Open (of class scala.reflect.internal.Types$ClassNoArgsTypeRef)?

为什么有一些想法?

编辑

使用抽象类型成员而不是类型参数:

import reflect.runtime.universe._
package stackoverflow3 {
  trait Mixin {
    sealed trait Status
    trait Open extends Status
    trait Closed extends Status
    case class Buffer(available: Int) {
      type S <: Status
      def withStatus[S <: Status] = asInstanceOf[BufferAux[S]]
    }
    type BufferAux[S0 <: Status] = Buffer { type S = S0 }
    object Buffer {
      def create(available: Int): BufferAux[Open] = Buffer(available).withStatus[Open]
      def from[S <: Status: WeakTypeTag](b: BufferAux[S]): BufferAux[Open] = {
        weakTypeOf[S] match {
          case t if t <:< weakTypeOf[Open]   => b.asInstanceOf[BufferAux[Open]]
          case t if t <:< weakTypeOf[Closed] => Buffer(b.available).withStatus[Open] // no need for creating new Buffer, only shows intend
        }
      }
    }
  }
}
package object stackoverflow3 extends Mixin
package stackoverflow3 {
  object Main extends App {
    val b1 = Buffer.create(1024)
    val b2 = Buffer.from(b1)
  }
}

让我们为Buffer.from方法添加一些调试打印:

import scala.reflect.runtime.universe.showRaw
println(s"scrut = ${weakTypeOf[S]}, ${showRaw(weakTypeOf[S])}")
println(s"Open = ${weakTypeOf[Open]}, ${showRaw(weakTypeOf[Open])}")
println(s"Closed = ${weakTypeOf[Closed]}, ${showRaw(weakTypeOf[Closed])}")

这将打印出有问题的类型及其结构。也许这可以解释发生了什么:

scrut = stackoverflow2.Open, TypeRef(SingleType(ThisType(stackoverflow2), stackoverflow2.package), TypeName("Open"), List())
Open = Mixin.this.Open, TypeRef(ThisType(stackoverflow2.Mixin), TypeName("Open"), List())
Closed = Mixin.this.Closed, TypeRef(ThisType(stackoverflow2.Mixin), TypeName("Closed"), List())

好吧,这显示了所讨论类型的完全限定版本,这揭示了可能存在问题的差异。所以,反思是说stackoverflow2.Open不是Mixin.this.Open的亚型。嗯,但是包对象stackoverflow2扩展了Mixin。那一定是反映中的错误,对吧?

让我们问第三方,看看scalac对此有什么看法。我们在Buffer.from中写一些类似val test: Mixin.this.Open = (??? : stackoverflow2.Open)的东西并尝试编译怎么样?

Test.scala:17: error: type mismatch;
 found   : stackoverflow2.Open
 required: Mixin.this.Open
        val test: Open = (??? : stackoverflow2.Open)
                              ^
one error found

好吧,scalac实际上同意反射,所以也许它不是一个bug。事实上,这并不是一个bug,因为如果我们创建Mixin的另一个子类,那么该子类将拥有自己的Mixin.this.Open副本,这将与stackoverflow2Mixin.this.Open副本不兼容。这意味着stackoverflow2.Open理所当然地不是Mixin.this.Open的亚型。

我们有几种选择。

首先,我们可以只写t <:< weakTypeOf[stackoverflow2.Open],而不是t <:< weakTypeOf[Open]。不漂亮,因为这把关于子类的知识放到了一个超类中。

其次,我们可以在weakTypeOf[Open]上使用asSeenFrom来将Mixin.this.Open调整为传递到from中的类型的容器。这将安抚子类型检查器,因为这样它将比较一棵树上的苹果和同一棵树的苹果,可以说:

type InternalType = scala.reflect.internal.SymbolTable#Type
type ApiType = Type
val pre = weakTypeOf[S].asInstanceOf[InternalType].prefix.asInstanceOf[ApiType]
val topen = weakTypeOf[Open].asSeenFrom(pre, symbolOf[Mixin])
...
case t if t <:< topen => b.asInstanceOf[Buffer[Open]]
...

第三,我们可以进行手动检查,以验证:a)我们传递的类型是在Mixin的子类型中声明的,b)底层符号的名称是OpenClosed。这似乎比第二种选择更容易,但实际上我不建议这样做,因为当你自己重新实现这样的东西时,很容易忽略一些角落的情况。例如,上面提供的配方实际上是错误的,因为除了a)和b)之外,我们还需要检查c)底层符号是从Mixin继承的,而不是重新定义的(即阴影)。我很可能忽略了其他事情,所以第三种选择绝对是风险最大的一种。

最新更新