最近我偶然发现了一个奇怪的(对我而言)编译器错误消息。请考虑以下代码:
trait Foo {
type Res <: Foo
type Bar[X <: Res]
}
class MyFoo extends Foo {
override type Res = MyFoo
override type Bar[X <: Res] = List[X]
}
type FOO[F <: Foo, R <: Foo, B[_ <: R]] = F { type Res = R;
type Bar[X <: R] = B[X] }
def process[F <: Foo, R <: Foo, B[_ <: R]](f: FOO[F, R, B]) {}
现在,如果我想调用process
方法,我必须显式写入类型参数:
process[MyFoo, MyFoo, List](new MyFoo) // fine
如果我写:
process(new MyFoo)
或
process((new MyFoo): FOO[MyFoo, MyFoo, List])
我收到以下错误消息:
推断的各种类型参数(MyFoo,MyFoo,List [X])不符合预期的类型参数类型(类型F,类型R,类型B)。 List [X]的类型参数与B类的预期参数不匹配:class List有一个类型参数,但类型B有一个
为什么编译器不能推断出类型(虽然我在调用参数中明确说明了它们)?那class List has one type parameter, but type B has one
是什么意思?有东西有一个,但另一个有也一个,这就是为什么它们不适合在一起???
答案 0 :(得分:3)
如果我们查看Scala编译器,源代码可以帮助我们理解问题所在。我从未为Scala编译器做过贡献,但我发现源代码非常易读,我已经对此进行了调查。
负责类型推断的类是scala.tools.nsctypechecker.Infer
,只需在Scala编译器源中查找错误的一部分即可找到它。您将找到以下片段:
/** error if arguments not within bounds. */
def checkBounds(pos: Position, pre: Type, owner: Symbol,
tparams: List[Symbol], targs: List[Type], prefix: String) = {
//@M validate variances & bounds of targs wrt variances & bounds of tparams
//@M TODO: better place to check this?
//@M TODO: errors for getters & setters are reported separately
val kindErrors = checkKindBounds(tparams, targs, pre, owner)
if(!kindErrors.isEmpty) {
error(pos,
prefix + "kinds of the type arguments " + targs.mkString("(", ",", ")") +
" do not conform to the expected kinds of the type parameters "+ tparams.mkString("(", ",", ")") + tparams.head.locationString+ "." +
kindErrors.toList.mkString("\n", ", ", ""))
}
所以现在要明白为什么checkKindBounds(tparams, targs, pre, owner)
会返回这些错误。如果沿着方法调用链向下,您将看到checkKindBounds调用另一个方法
val errors = checkKindBounds0(tparams, targs, pre, owner, true)
您将看到问题与检查更高级别类型的边界有关,在第5784行,checkKindBoundsHK内部:
if (!sameLength(hkargs, hkparams)) {
if (arg == AnyClass || arg == NothingClass) (Nil, Nil, Nil) // Any and Nothing are kind-overloaded
else {error = true; (List((arg, param)), Nil, Nil) } // shortcut: always set error, whether explainTypesOrNot
}
测试没有通过,它出现在我的调试器中:
hkargs$1 = {scala.collection.immutable.Nil$@2541}"List()"
arg$1 = {scala.tools.nsc.symtab.Symbols$ClassSymbol@2689}"class List"
param$1 = {scala.tools.nsc.symtab.Symbols$TypeSymbol@2557}"type B"
paramowner$1 = {scala.tools.nsc.symtab.Symbols$MethodSymbol@2692}"method process"
underHKParams$1 = {scala.collection.immutable.$colon$colon@2688}"List(type R)"
withHKArgs$1 = {scala.collection.immutable.Nil$@2541}"List()"
exceptionResult12 = null
hkparams$1 = {scala.collection.immutable.$colon$colon@2688}"List(type R)"
所以看起来有一个更高的kinded param,类型R,但是没有为此提供的值。
如果你真的回到checkKindBounds,你会在片段之后看到:
val (arityMismatches, varianceMismatches, stricterBounds) = (
// NOTE: *not* targ.typeSymbol, which normalizes
checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)
)
arityMismatches
包含一个元组列表,B。现在您还可以看到错误消息是错误的:
推断出各种类型的参数(MyFoo,MyFoo,List [X])都没有 符合预期种类的参数(类型F,类型 R,B型)。列表[X]的类型参数与B类型的预期不匹配 参数:class List有一个类型参数,但类型B有 ZERO
事实上,如果你在下一次电话
的第5859行设置断点checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)
你可以看到
tparam = {scala.tools.nsc.symtab.Symbols$TypeSymbol@2472}"type B"
targ = {scala.tools.nsc.symtab.Types$UniqueTypeRef@2473}"List[X]"
结论:
出于某种原因,在处理像你这样复杂的高级类型时,Scala编译器推断是有限的。我不知道它的来源,也许你想向编译器团队发送一个错误
答案 1 :(得分:0)
我对Scala中类型推导者的确切运作只有模糊的理解,所以请考虑这些想法并不是明确的答案。
类型推断在一次推断多个类型时存在问题。
您在FOO的定义中使用存在类型,其转换为:存在一种类型,不确定它是否与MyFoo中给出的特定类型兼容