Простое присутствие неявного преобразования заставляет программу компилироваться, несмотря на то, что она никогда не применялась. - PullRequest
9 голосов
/ 16 июня 2020

Рассмотрим метод f, который параметризуется конструктором типа F[_] и надлежащим типом A

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

Давайте попробуем применить его к 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]

Эта ошибка, как и ожидалось, 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

Это компилируется, однако обратите внимание, что неявное преобразование, похоже, на самом деле не применялось, потому что класс среды выполнения res1 это Bar, а не List. Кроме того, типом времени компиляции res1 является Any, а не List[Int]. Глядя на вывод -Xprint:typer, мы видим что-то вроде

val res1: Any = f[Any, Nothing](new Bar())

, где мы видим, что произошел следующий вывод:

F[_] = Any
A = Nothing 

в отличие от

F[_] = List
A = Int 

, и мы видим, что на самом деле никакого преобразования не произошло, то есть мы не видим чего-то вроде

f(barToList(new Bar()))

Почему простое присутствие неявного преобразования заставило программу компилироваться, в то время как неявное преобразование фактически не применялось? Обратите внимание, что при явном указании параметров типа он работает как ожидалось

scala> f[List, Int](new Bar)
val res2: List[Int] = List(42)

1 Ответ

9 голосов
/ 17 июня 2020

Я заметил эту проблему раньше, и я думаю, что ее можно отследить до этого кода в компиляторе:

  // 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))

Насколько я понимаю, проблема в том, что проверка isCompatible ищет неявные преобразования, но не отслеживает, требовалось оно или нет, а solvedType просто проверяет границы.

Итак, если у вас нет неявного преобразования, isCompatible(Bar, F[A]) является ложным, и вызов methTypeArgs вызывает исключение DeferredNoInstance - он даже не рассматривает Any в качестве кандидата на F.

Если у вас есть неявное преобразование, isCompatible(Bar, F[A]) верно, но компилятор сразу забывает, что это правда только из-за неявного преобразования, и solvedTypes выбирает Any вместо F, что разрешено из-за странного особого полиморфизма вида Scala. для Any. В этот момент значение Bar совершенно нормально, так как это Any, и компилятор не применяет преобразование, найденное в isCompatible (которое он забыл, что он нужен).

( В качестве примечания: если вы укажете явные параметры типа для f, methTypeArgs вообще не вызывается, и это несоответствие между isCompatible и solvedTypes не имеет значения.)

Я думаю, что это должна быть ошибка в компиляторе. Не знаю, сообщал ли кто-нибудь об этом (я просто смотрел пять минут и не видел).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...