Как компилятор Scala выполняет неявное преобразование? - PullRequest
7 голосов
/ 15 апреля 2019

У меня есть собственный класс, A, и я определил некоторые операции внутри класса следующим образом:

def +(that: A) = ...
def -(that: A) = ...
def *(that: A) = ...

def +(that: Double) = ...
def -(that: Double) = ...
def *(that: Double) = ...

Для того, чтобы что-то вроде 2.0 + x имело смысл, когда x имеет тип A, я определил следующий неявный класс:

object A {
  implicit class Ops (lhs: Double) {
    def +(rhs: A) = ...
    def -(rhs: A) = ...
    def *(rhs: A) = ...
  }
}

Это все нормально работает. Теперь я представляю плагин компилятора с TypingTransformer, который выполняет некоторые оптимизации. В частности, скажем, у меня есть ValDef:

val x = y + a * z

, где x, y и z имеют тип A, а a представляет собой Double. Обычно это компилируется нормально. Я поставил его через оптимизатор, который использует квазицитаты, чтобы изменить y + a * z на что-то еще. НО в этом конкретном примере выражение остается неизменным (оптимизация не выполняется). Внезапно компилятор больше не выполняет неявное преобразование для a * z.

Подводя итог, у меня есть плагин компилятора, который принимает выражение, к которому обычно применяются неявные преобразования. Он создает новое выражение с помощью квазиквот, которое синтаксически выглядит так же, как старое выражение. Но для этого нового выражения компилятору не удалось выполнить неявное преобразование.

Итак, мой вопрос - как компилятор определяет необходимость неявного преобразования? Есть ли в AST определенный флаг или что-то, что нужно установить, а квазицитаты не могут быть установлены?


UPDATE

Фаза плагина выглядит примерно так:

override def transform(tree: Tree) = tree match {
  case ClassDef(classmods, classname, classtparams, impl) if classname.toString == "Module" => {
    var implStatements: List[Tree] = List()
    for (node <- impl.body) node match {
      case DefDef(mods, name, tparams, vparamss, tpt, body) if name.toString == "loop" => {
        var statements: List[Tree] = List()
        for (statement <- body.children.dropRight(1)) statement match {
          case Assign(opd, rhs) => {
            val optimizedRHS = optimizeStatement(rhs)
            statements = statements ++ List(Assign(opd, optimizedRHS))
          }
          case ValDef(mods, opd, tpt, rhs) => {
            val optimizedRHS = optimizeStatement(rhs)
            statements = statements ++
              List(ValDef(mods, opd, tpt, optimizedRHS))
          }
          case Apply(Select(src1, op), List(src2)) if op.toString == "push" => {
            val optimizedSrc2 = optimizeStatement(src2)
            statements = statements ++
              List(Apply(Select(src1, op), List(optimizedSrc2)))
          }
          case _ => statements = statements ++ List(statement)
        }

        val newBody = Block(statements, body.children.last)
        implStatements = implStatements ++
          List(DefDef(mods, name, tparams, vparamss, tpt, newBody))
      }
      case _ => implStatements = implStatements ++ List(node)
    }
    val newImpl = Template(impl.parents, impl.self, implStatements)
    ClassDef(classmods, classname, classtparams, newImpl)
  }
  case _ => super.transform(tree)
}

def optimizeStatement(tree: Tree): Tree = {
  // some logic that transforms
  // 1.0 * x + 2.0 * (x + y)
  // into
  // 3.0 * x + 2.0 * y
  // (i.e. distribute multiplication & collect like terms)
  //
  // returned trees are always newly created
  // returned trees are create w/ quasiquotes
  // something like
  // 1.0 * x + 2.0 * y
  // will return
  // 1.0 * x + 2.0 * y
  // (i.e. syntactically unchanged)
}

ОБНОВЛЕНИЕ 2

Пожалуйста, обратитесь к этому репозиторию GitHub для минимального рабочего примера: https://github.com/darsnack/compiler-plugin-demo

Проблема в том, что a * z превращается в a.<$times: error>(z) после того, как я оптимизирую оператор.

1 Ответ

4 голосов
/ 04 мая 2019

Проблема связана с полем pos, связанным с деревьями. Даже если все происходит до namer, а дерево с плагином компилятора и без него синтаксически одинаково, компилятор не сможет вывести неявное преобразование из-за этой надоедливой строки в источнике компилятора:

val retry = typeErrors.forall(_.errPos != null) && (errorInResult(fun) || errorInResult(tree) || args.exists(errorInResult))

(кредит hrhino за это).

Решение состоит в том, чтобы всегда использовать treeCopy при создании нового дерева, чтобы скопировать все внутренние флаги / поля:

case Assign(opd, rhs) => {
  val optimizedRHS = optimizeStatement(rhs)
  statements = statements ++ List(treeCopy.Assign(statement, opd, optimizedRHS))
}

И при генерации дерева с использованием квазицитат не забудьте установить положение:

var optimizedNode = atPos(statement.pos.focus)(q"$optimizedSrc1.$newOp")

Я обновил репозиторий MWP Github с помощью фиксированного решения: https://github.com/darsnack/compiler-plugin-demo

...