尽管从未应用,但仅存在隐式转换就可以编译程序

Mar*_*lic 10 scala type-inference implicit-conversion type-parameter type-constructor

考虑f由类型构造函数F[_]和适当类型参数化的方法A

def f[F[_], A](v: F[A]) = v
Run Code Online (Sandbox Code Playgroud)

让我们尝试将其应用于 new Bar

scala> class Bar
class Bar

scala> def f[F[_], A](v: F[A]) = v
def f[F[_], A](v: F[A]): F[A]

scala> f(new Bar)
       ^
       error: no type parameters for method f: (v: F[A]): F[A] exist so that it can be applied to arguments (Bar)
        --- because ---
       argument expression's type is not compatible with formal parameter type;
        found   : Bar
        required: ?F[?A]
         ^
       error: type mismatch;
        found   : Bar
        required: F[A]
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,此错误Bar的形状不正确。

现在让我们添加一个从Bar到的隐式转换List[Int]

scala> implicit def barToList(b: Bar): List[Int] = List(42)
def barToList(b: Bar): List[Int]

scala> f(new Bar)
val res1: Any = Bar@56881196
Run Code Online (Sandbox Code Playgroud)

这可以编译,但是请注意,实际上似乎并未应用隐式转换,因为res1isBar和 not的运行时类List。此外, 的编译时类型res1isAny和 not List[Int]。查看-Xprint:typer我们的输出,我们看到类似

val res1: Any = f[Any, Nothing](new Bar())
Run Code Online (Sandbox Code Playgroud)

我们看到以下推理发生的地方

F[_] = Any
A = Nothing 
Run Code Online (Sandbox Code Playgroud)

F[_] = List
A = Int 
Run Code Online (Sandbox Code Playgroud)

我们看到实际上没有发生任何转换,也就是说,我们没有看到类似的东西

f(barToList(new Bar()))
Run Code Online (Sandbox Code Playgroud)

为什么仅仅存在隐式转换就可以编译程序,而实际上没有应用隐式转换?请注意,当明确类型参数时,它会按预期工作

scala> f[List, Int](new Bar)
val res2: List[Int] = List(42)
Run Code Online (Sandbox Code Playgroud)

Tra*_*own 10

我以前注意到这个问题,我认为它可以追溯到编译器中的这段代码

  // Then define remaining type variables from argument types.
  foreach2(argtpes, formals) { (argtpe, formal) =>
    val tp1 = argtpe.deconst.instantiateTypeParams(tparams, tvars)
    val pt1 = formal.instantiateTypeParams(tparams, tvars)

    // Note that isCompatible side-effects: subtype checks involving typevars
    // are recorded in the typevar's bounds (see TypeConstraint)
    if (!isCompatible(tp1, pt1)) {
      throw new DeferredNoInstance(() =>
        "argument expression's type is not compatible with formal parameter type" + foundReqMsg(tp1, pt1))
    }
  }
  val targs = solvedTypes(tvars, tparams, varianceInTypes(formals), upper = false, lubDepth(formals) max lubDepth(argtpes))
Run Code Online (Sandbox Code Playgroud)

据我了解,问题在于isCompatible检查会寻找隐式转换,但不会跟踪是否需要转换,而solvedType只是查看边界。

因此,如果您没有隐式转换,isCompatible(Bar, F[A])则为 false 并且methTypeArgs调用会抛出DeferredNoInstance异常——它甚至不会被Any视为F.

如果您确实有隐式转换,isCompatible(Bar, F[A])则为真,但编译器会立即忘记它只是因为隐式转换才为真,并solvedTypes选择Anyfor F,这是允许的,因为 Scala 奇怪的特殊情况 for 种类多态性Any。那时该Bar值非常好,因为它是一个Any,并且编译器不会应用它在isCompatible其中找到的转换(它忘记了它需要)。

(作为一个方面说明,如果你提供明确的类型参数fmethTypeArgs完全不叫之间,以及这种差异isCompatiblesolvedTypes无关。)

我认为这一定是编译器中的错误。不知道有没有人举报过(我只是看了五分钟没看到)。